Comments 105
Извините, а причем тут C#?(в хабах)
Я рассуждал так, что перечисленные языки очень похожи, и хотелось увидеть схожие решение на с#
static double Calc(string expr)
{
return (from e in new[] {expr}
let e_clear = e.Replace(" ", "")
let match = Regex.Match(e_clear.Substring(1), @"[+\-*/]")
let operation = match.Value
let operation_index = match.Index + 1
let first = double.Parse(e_clear.Substring(0, operation_index))
let second = double.Parse(e_clear.Substring(operation_index + 1))
let result =
operation == "+"
? first + second
: (operation == "-"
? first - second
: (operation == "*" ? first*second : (operation == "/" ? first/second : double.NaN)))
select result).FirstOrDefault();
}
JS можно?
вот без eval:
((s)=>({'+':(a,b)=>+a+(+b),'-':(a,b)=>a-b,'*':(a,b)=>a*b,'/':(a,b)=>a/b})[s[1]](s[0],s[2]))('9/3')
уупс, только с одним разрядом и с положительными работает, сейчас поправлю
Python 2:
while True:
print input('> ')
Python 3:
while True:
print(eval(input('> ')))
Python (любой):
import sys
while True:
if sys.version_info >= (3, 0):
print(eval(input('> ')))
else:
print(input('> '))
trollface.jpg
Ruby:
puts eval gets
С циклом:
loop do puts eval gets end
Основная идея похожа на то, что приводили выше на JS, но за счёт нетривиального использования генератора списка позволяет избежать импорта вообще.
def calc(line):
print([item[1](int(line.split(item[0])[0]), int(line.split(item[0])[1])) for item in {'*': lambda x,y: x*y, '+': lambda x,y: int(x)+int(y), '-': lambda x,y: x-y, '/': lambda x,y: x/y}.items() if item[0] in line][0])
Собственно, идея понятна. Считаем список, в котором будут результаты умножения чисел (если есть `*`), сложения (если есть `+`) и так далее. Поскольку по условиям задачи команда только одна, достаточно взять из этого списка первый элемент — он всё равно окажется единственным.
До полноценного однострочника не хватает только убрать объявление функции. Код и без того не особо читаемый, так что приведу только идею. (голосом парня из Креосана) Нужно больше генераторов!
[all_that_stuff_above for line in [input('> ')]][0]
Очевидно, `all_that_stuff_above` — это тело функции сверху. Пока только не могу сообразить, как ввернуть сюда бесконечный цикл.
есть модуль оператор чтобы не оборачивать все лямбдами
И, это, прошу больно не бить — пока ещё только изучаю Яву :)
import java.io.PrintStream;
import java.util.function.BinaryOperator;
import java.util.stream.*;
public class OneLineCalc
{
private static void calc(String s)
{
try
{
System.out.println
(
Stream.of(BinaryOperator.maxBy(Double::compare), (a,b) -> a+b, (a,b) -> a-b, (a,b) -> a*b, (a,b) -> a/b)
.collect(Collectors.toList())
.get
(
IntStream.iterate(1, i -> i + 1)
.limit(s.replaceAll("\\s", "").length() - 1)
.map(i -> 1 + "+-*/".indexOf(s.replaceAll("\\s", "").charAt(i)))
.filter(i -> i > 0)
.limit(1)
.sum()
)
.apply
(
IntStream.iterate(1, i -> i + 1)
.limit(s.replaceAll("\\s", "").length() - 1)
.filter(i -> "+-*/".indexOf(s.replaceAll("\\s", "").charAt(i)) >= 0)
.limit(1)
.mapToDouble(i -> Double.parseDouble(s.replaceAll("\\s", "").substring(0, i)))
.sum(),
IntStream.iterate(1, i -> i + 1)
.limit(s.replaceAll("\\s", "").length() - 1)
.filter(i -> "+-*/".indexOf(s.replaceAll("\\s", "").charAt(i)) >= 0)
.limit(1)
.mapToDouble(i -> Double.parseDouble(s.replaceAll("\\s", "").substring(i+1)))
.sum()
)
);
}
catch (Exception e)
{
try (PrintStream stream = (System.out.append("Nan"))) { }
}
}
public static void main(String[] args)
{
calc("+5 + -12");
calc("+5 * -12");
calc("+5 - -12");
calc("+5 / -12");
}
}
Вы имели в виду как-то так?
import java.util.*;
import java.util.function.DoubleBinaryOperator;
import java.util.stream.*;
public class OneLineCalc {
private static final String OP_NAMES = "+-*/";
private static final List<DoubleBinaryOperator> OPS =
Arrays.asList((a, b) -> a+b, (a, b) -> a-b, (a, b) -> a*b, (a, b) -> a/b);
private static String normalize(String s) {
return s.replaceAll("\\s", "");
}
private static void calc(String s) {
System.out.println(doCalc(normalize(s)));
}
private static double doCalc(String s) {
try {
int opPos = IntStream.range(1, s.length())
.filter(idx -> OP_NAMES.indexOf(s.charAt(idx)) != -1)
.findFirst().getAsInt();
return OPS.get(OP_NAMES.indexOf(s.charAt(opPos))).applyAsDouble(
Double.parseDouble(s.substring(0, opPos)),
Double.parseDouble(s.substring(opPos+1)));
}
catch (NoSuchElementException | NumberFormatException e) {
return Double.NaN;
}
}
public static void main(String[] args) {
calc("+5 + -12");
calc("+5 * -12");
calc("+5 - -12");
calc("+5 / -12");
}
}
я имел в виду те условия, которые описываются автором:
Необходимо написать калькулятор для простого выражения, который бы содержал ровно 1 строчку кода и умел складывать, вычитать, умножать и делить 2 числа. 1 строчка — означает ровно 1 точку с запятой
и кстати, я упростил свой вариант функции calc():
IntStream.iterate(1, i -> i + 1)
.limit(expr.replaceAll("\\s", "").length() - 1)
.filter(i -> "+-*/".indexOf(expr.replaceAll("\\s", "").charAt(i)) >= 0)
.limit(1)
.mapToDouble
(
i -> Stream.of
(
BinaryOperator.maxBy(Double::compare),
(a,b) -> a + b, (a,b) -> a - b, (a,b) -> a * b, (a,b) -> a / b
)
.collect(Collectors.toList())
.get(1 + "+-*/".indexOf(expr.replaceAll("\\s", "").charAt(i)))
.apply
(
Double.parseDouble(expr.replaceAll("\\s", "").substring(0, i)),
Double.parseDouble(expr.replaceAll("\\s", "").substring(i + 1))
)
)
.forEach(f -> System.out.println(f));
Понял. С помощью Optional и flatMap легко наплодить вложенных скоупов в одном выражении, например, так:
System.out.println(Optional.of(str)
.map(s -> s.replaceAll("\\s", ""))
.flatMap(s -> IntStream.range(1, s.length())
.filter(idx -> "+-*/".indexOf(s.charAt(idx)) != -1)
.boxed().findFirst().map(opPos -> Arrays.<DoubleBinaryOperator>asList(
(a, b) -> a+b, (a, b) -> a-b, (a, b) -> a*b, (a, b) -> a/b)
.get("+-*/".indexOf(s.charAt(opPos))).applyAsDouble(
Double.parseDouble(s.substring(0, opPos)),
Double.parseDouble(s.substring(opPos+1)))))
.orElse(Double.NaN));
Остаётся обработка исключения при parseDouble. Мне не нравится оригинальный вариант, это всё же не "одно выражение". Можно использовать CompletableFuture
для обработки исключений:
ToDoubleFunction<String> parser =
s -> CompletableFuture.completedFuture(s)
.thenApply(Double::parseDouble)
.handle((d, t) -> t != null ? Double.NaN : d).join();
При этом парсер можно опять же закинуть в скоуп через Optional, чтобы избежать дублирования кода. Окончательно:
System.out.println(Optional.<ToDoubleFunction<String>>of(s ->
CompletableFuture.completedFuture(s).thenApply(Double::parseDouble)
.handle((d, t) -> t != null ? Double.NaN : d).join())
.flatMap(parser -> Optional.of(str)
.map(s -> s.replaceAll("\\s", ""))
.flatMap(s -> IntStream.range(1, s.length())
.filter(idx -> "+-*/".indexOf(s.charAt(idx)) != -1)
.boxed().findFirst().map(opPos -> Arrays.<DoubleBinaryOperator>asList(
(a, b) -> a+b, (a, b) -> a-b, (a, b) -> a*b, (a, b) -> a/b)
.get("+-*/".indexOf(s.charAt(opPos))).applyAsDouble(
parser.applyAsDouble(s.substring(0, opPos)),
parser.applyAsDouble(s.substring(opPos+1))))))
.orElse(Double.NaN));
Действительно одно выражение и почти без дублирования кода.
большое спасибо за комментарий и куски кода :)
теперь уже покрасивше…
package onelinecalc;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.function.BinaryOperator;
import java.util.stream.*;
public class OneLineCalc
{
private static void calc(String s)
{
try
{
Arrays.asList(s.replaceAll("\\s", "")).forEach
(
expr -> System.out.println
(
IntStream.range(1, expr.length())
.filter(i -> "+-*/".indexOf(expr.charAt(i)) >= 0)
.mapToDouble
(
i -> Arrays.<BinaryOperator<Double>>asList
( (a,b) -> a + b, (a,b) -> a - b, (a,b) -> a * b, (a,b) -> a / b )
.get("+-*/".indexOf(expr.charAt(i)))
.apply
(
Double.parseDouble(expr.substring(0, i)),
Double.parseDouble(expr.substring(i + 1))
)
)
.findFirst().orElse(Double.NaN)
)
);
}
catch (Exception e)
{
try ( PrintStream stream = new PrintStream(System.out).append("NAN") ) { }
}
}
public static void main(String[] args)
{
calc("+5 + -12");
calc("+5 - -12");
calc("+5 * -12");
calc("+5 / -12");
}
}
по сути у Вас то же самое, но «лишняя точка с запятой» :)
и за .findFirst() спасибо, я не был уверен, что он отработает именно так, как мне надо — чтобы без .limit().
потестю вечерком этот момент с iterate() + findFirst().
и про range() я что-то забыл, вот оно что…
Вариант с простым лексером (на два типа токенов: число и знак операции) и простым парсером без приоритетов операций без опыта такого рода вещей (в смысле написания парсеров и подобной алгоритмики) из общих соображений пишется за час-полтора (с тестами и перекурами). Вчера проверял ,)
Ради спортивного интереса: можно пожалуйста в студию ваш код?
Я специально не смотрел чужие решения, чтобы собрать своих граблей и посмотреть где именно они будут.
Вечером постараюсь не забыть закинуть в gist.
Мой вариант: https://gist.github.com/grossws/b37cef5f24489dd63ac58cce718c79c1.
Все бинарные операции правоассоциативны, поэтому в некоторых тестах есть неочевидная хрень: -0.2 + -0.3 * 5.00 == -1.7
, но 5. * -0.3 - 0.2 == -2.5
.
Общее время написания около 1.5 часов, если правильно помню. Из которых минут 15 ушло на поиск неприятного бага из-за неявного lookahead'а в токенайзере, который не проявлялся, если операции отбиты пробелами.
Ну и тесты для парсера по-хорошему надо было писать так, чтобы кормить их спискасм токенов, а не строчками, если по канонам unit-тестирования, но я был ленив и написал к тому моменту токенайзер с его тестами.
Дополнение: в контексте такой задачи на собеседовании сначала бы написал тривиальный вариант на 1 регулярке и 3 if'а, а потом бы развлекался с лексером и парсером. Или взял бы antlr, например ,)
Для этой задачи почти всё перебор кроме простых автоматов (ручных или внутри регулярки).
А если развлекаться или работа предполагает написание парсеров, то в качестве дополнения к простому решению можно и с antlr/javacc поиграться. Или реализовать более сложную грамматику, как вариант.
Тут можно найти мой код на эту тему. Могу сказать, что написание полностью работающего калькулятора с приоритетами, скобками и кучей операций заняло у меня где-то в районе 8 часов (поездка на Сапсане Мск-СПб и обратно). За полтора часа можно написать упрощённый вариант, который будет читаемее предложенных и в котором не будет багов.
— никому не нужен реально работающий калькулятор, пусть хоть там два абстрактных синтаксических дерева внутри будет
— нужна ваша способность понимать, какие средства применимы к конкретной задаче, и почему
— можно ли сделать на регулярных выражениях, почему да, и когда нет?
— как сделать конечный автомат, и какие выражения он способен разбирать
— как написать хоть какой-то парсер
Человек, который претендует на что-то большее, чем быдлокодер (а иначе, нафига вообще давать такую задачу?), должен быть способен рассказать про рекурсивный спуск. А если сможет еще про что-то, типа parsec — будет идеально. Оба — прямо скажем тривиальны.
Ну и все это применить к конкретной задаче, и показать (с оценками в часах), что ее можно сделать на регулярках, или на стримах, и почему написание LL(1) тут будет уже перебором.
И можно код вообще не писать.
А ваша позиция пригодна для тестирования студента на знание алгоритмов и выявление потенциала его как программиста.
С опытом приходит понимание что все это детские игры. Я с подобных собеседований ухожу сразу. Ибо работать там скорее всего будет не комфортно.
Главное для реально хорошего программиста — это умение находить решение оптимальное для задачи, а не заучивать регулярные выражения и вариации парсеров. Поиск и обработка информации — наше все.
И вообще основной смысл в том что не стоит оценивать программиста по знаниям из справочников. Это работает только если ищете узкого специалиста в конкретной области.
Но человек, который претендует быть программистом, просто обязан понимать, что регулярные выражения, скажем, либо совсем неприменимы для разбора языков с вложенными структурами, либо сильно ограничены для таких задач. Что внутри у них, упрощенно — конечный автомат, а стека наоборот внутри нет. Он просто обязан знать, что такое грамматики, и какими хотя бы примерно методами с ними работают.
Это вовсе не нужно, чтобы рисовать сайты, но если уж такую задачу, как тут описано, вообще человеку дают — я бы как раз от него ожидал хоть какого-то понимания теории. Не знания алгоритмов — а понимания, какие они могут быть в такой задаче, и почему надо выбрать тот или оной.
Я например несмотря на то что учился в институте на профессию инженер-программист — грамматики не изучал. В работе мне их знание никогда не требовалось. Причем я не веб-разработчик. А программист на C++ в энтерпрайзе.
Во всех местах где я работал была совершенно новая для меня предметная область. Например в текущем месте работа криптография и смарт-карты. Это не помешало мне в ней разобраться и работать ведущим разработчиком.
Непосредственные заученные знания это пыль на ветру. Развеивается сразу после заучивания. Важно знать основы. Методологию. Изучать их постоянно. И главное — то чему меня в моем универе научили — это уметь получать новые знания когда требуется.
По вашему описанию постановки задачи — соискателю должен быть дан еще и доступ к интернету и время для поиска.
Языки и грамматики, а также конечные автоматы — это не заученные знания. Это структуры данных, и это самые базовые алгоритмы, типа работы с множествами. Это азы, по большому счету.
По поводу языков и грамматик — это типичнейшее заблуждение выпусников постсоветских вузов. Это совсем не базовые структуры и незнание их ничего не меняет ибо действительно разбираться в этом требуется только в узкой области разработки.
Что не отменяет полезности общих знаний по данной теме, как и вообще постоянной учебы.
Что же до грамматик — то что вы знаете о том, какой ВУЗ я закончил? И да, их базовость состоит не в том, что они везде нужны — а в том, что они, по сути, достаточно тривиальны, и никакого ricket science не содержат. И если человек их не знает вовсе — это если не повод его браковать, то повод задуматься о его квалификации.
По поводу ВУЗа — я сужу по тому что грамматики и языки программистам стали преподавать после развала союза. До этого в основном налегали на базовую математическую основу. И единственный вариант что вы их изучали в то время — тот что вы учились на другой специальности — например «Прикладная математика».
По поводу нужности грамматик — я с вами расхожусь кардинально. Я считаю что они нужны только для общего образования и к квалификации не имеют никакого отношения. И проблема не в тривиальности а в том что все знать невозможно. И за 10 лет работы в разработке вы можете столкнутся с тем что они вам ни разу не понадобятся.
Область эта не узкая. У меня 90% задач состоят и состояли в том, чтобы парсить или генерировать какие-то потоки данных, текстовые или бинарные, от других приложений, к которым нет API. Считать конечные автоматы узкой областью (или того пуще — регулярные выражения) — это странно.
Вы, собственно, пишете на каком-то языке — и вам не любопытно, как устроен компилятор? Не, вы не обязаны — но неужели любопытства никогда не проявлялось?
И вы кстати ошибаетесь насчет ВУЗ-а. Я вообще по специальности инженер конструктор. 1981 год. Компиляторы у нас преподавали уже тогда. Не нам. Нам преподавали сопромат.
Для уточнение расскажите о своей области. Пока у меня складывается мнение что именно узкая или вообще веб-разработка.
По поводу любопытства вы явно не читаете что я написал. Я же сказал что для общего образования и развития желательно изучать. Но на собеседованиях спрашивать только в исключительных случаях, мной описанных.
По поводу ВУЗа — вы опять невнимательно прочитали. Я именно что прав и вы это подтвердили. Так как получили образование инженера конструктора. Я учился по программе инженера программиста в университете — там грамматик и языков не было. А то что называется компиляторы — тоже было но без грамматик и конечных автоматов и тем более без написания компиляторов. Только базовые основы. Из математики только машина тьюринга.
Я например несмотря на то что учился в институте на профессию инженер-программист — грамматики не изучал. В работе мне их знание никогда не требовалось.
Здраво оценивать область применимости неких знаний может только человек, этими знаниями владеющий. Хотя бы на базовом уровне.
Иначе получаются рассуждения о нужности обуви жителям заполярья от человека, всю жизнь проходившего босиком в тропиках.
Я считаю что нет. При этом для общего образования надо знать на базовом уровне.
Но есть один момент грамматики это вещь ИМХО довольно узкая и нужна для разработчиков языков например и еще для нескольких таких же узких областей.
И в дополнение — если вы умеете учиться (а это основной навык хорошего программиста) то всегда быстро научитесь как что-либо применять. Да и не забывайте мы живем в мире интернета — а значит там можно выяснить что в решении стоящей перед вами задачи подойдут грамматики, после чего их можно будет изучить.
Что касается искусства: второй и третий пример хорошо воспринимают строку
calc("ыы5*2");
Сколько времени понадобится здоровому человеку, чтобы среди этой прекрасной лапши исправить причину?
Вот интересно, а что ожидал увидеть интервьювер, давая такое бесполезное задание? И совпало ли ожидание с реальностью?
Если бы я проводил интервью с таким заданием, то я бы посмотрел на лаконичность и понятность кода, наличие комментариев / документации к методам, именование переменных и соблюдение стандартов оформления, отсутствие повторяющегося кода, лёгкость модификации.
Чтоб вам всю жизнь такой код поддерживать! За такой код в продакшине нужно убивать
А где здесь хоть пол-слова о продакшене? Вы техническое задание (статью) не читали вовсе, что ли? А свои технические задания так же читаете? ;)
интервьювер, давая такое бесполезное задание?
Для кого бесполезное? Для компании? В смысле, код соискателя бесплатно не потыришь и не вставишь в продакшн (а, я теперь понял почему вы про продакшен говорили)? Для этой цели абсолютно бесполезен. В точку!
Если бы я проводил интервью с таким заданием, то я бы посмотрел на лаконичность и понятность кода, наличие комментариев / документации к методам, именование переменных и соблюдение стандартов оформления, отсутствие повторяющегося кода, лёгкость модификации.
После получения такого решения на эту вашу муть можно не смотреть. Вы просто ищите работника в другом классе. В ортогональном. Это не хорошо и не плохо, ваш класс задач тоже имеет право на существование. Тут странно только одно — чего это вас так взбесил сам факт существования другой вселенной. По-моему, получив такое решение, в вашем случае стоило бы крепко пожать руку соискателю и сказать, — дорогой друг, вам, скорее всего, у нас будет бесконечно скучно и нас будут терзать угрызения совести от того, что вы из-за нас терпите такую муку, давайте не будем причинять друг-другу боль :).
ЗЫ, думаю, автору отказали, но по другой причине…
По мне, так после получения такого кода логично спросить "а как бы вы написали на самом деле" и принимать решение по результатам.
И забыть про фразу "удивите меня".
Хотя на будущее код можно сохранить и давать соискателям задание отрефакторить его.
Можно и так, если совсем делать нечего и есть непреодолимое желание измучить соискателя вусмерть. Только смысл не совсем понятен. Вы полагаете, человек, заморочившийся на написание такого кода не сможет сделать это: "лаконичность и понятность кода, наличие комментариев / документации к методам, именование переменных и соблюдение стандартов оформления, отсутствие повторяющегося кода, лёгкость модификации"? Серьёзно? Ну, это, конечно ваше право, но в тех конторах, куда я ходил до сих пор на собеседования и собеседователи попадались более профессиональные и относились они к претендентам более уважительно. Наверное, везло. Пусть и дальше везёт.
код можно сохранить и давать соискателям задание отрефакторить его
На этом месте я сразу вспомнил местную хабрадискуссию нескольколетней давности, когда пэхпешники всерьёз обсуждали вопрос о том, что простейший тернарный оператор слишком сложен для понимания и его следует "отрефакторить" в if-else конструкцию длиной от 5 до 8 строчек. :)
Это действительно 2 разные вселенные. Сам лично видел несколько программистов, которые были реально круты в решении олимпиадных задач, но также бесполезны в написании продакшн-кода.
Когда я иду на интервью, то ожидаю задания не на проверку моей памяти и решению задач про «почему люки круглые», а те задачи, которые покажут насколько я буду полезен компании и качество моей работы.
Сначала я подумал, что подобные решения могут быть интересны чтобы выъявить кандидатов для задач в научной и research сферах. Но всё-же и там важнее не написать в одну строчку, а написать так, чтобы потом было легко модифицировать, отлаживать и поддерживать.
Почему олимпиадные? Интервьюер получил ровно то, что получил. Ни каплей больше, ни каплей меньше.
Соискатель же строго в рамках сформулированного задания показал а) умение программировать б) умение мыслить в) умение мыслить нестандартно г) умение рассчитать свои силы, придумать решение, спроектировать и выполнить нетривиальным образом задание практически в строго отведённый срок и тд и тп.
Не знаю как вам, но это прекрасные навыки к отличного инженера. В большинстве случаев вообще достаточно пункта "а", а уж пункт "г" лично меня повергает прямо таки в священный трепет! :)
Ваши требования — умение оформлять код (нажать две плюс две кнопки? офигенное умение!), умение писать комментарии, умение именовать переменные. Это требования на какую, простите, должность? На должность стажёра, пытающегося устроиться за еду для строчки в резюме?
Да, ладно!
"умение оформлять код" вообще никому не нужно — это одна кнопка в IDE.
"умение именовать переменные" чуть более, чем ненужно. Код метода должен умещаться на экран, все переменные видны и что-то не понять сложно. Намного важнее умение именовать методы. И не писать их длиннее, чем на один экран :)
"умение писать комментарии"… даже не знаю что сказать… оно проистекает из неумения писать ясный код, правда же? :)
Ценно неупомянутое умение писать джавадоки к апи. Но таким способом его не проверишь. И это, скорее, не к очень сильно рядовому разработчику (а требования у zenkz относятся не более, чем к таковому), а к архитектору.
Как у вас всё круто, оформление кода — это не оформление кода, а другое, умение именовать переменные — это не умение именовать переменные, а другое, и умение писать комментарии — это не умение писание комментарии, а другое. Одни пишу, два — в уме! Чтобы так сформулировать, надо особыми талантами обладать. Вы ТЗ, случайно не пишите, :)
А теперь смотрим мой комментарий — ваше "умение оформлять код" — это "моё" (и фактическое) умение писать код (умение программировать). Ваше "умение именовать переменные" — это "моё" (и фактическое) умение писать код (умение программировать). Ваше "умение писать комментарии" — это "моё" умение писать джавадоки для апи. Для API. Для рутинного кода достаточно имени класса и метода.
Но всё-же и там важнее не написать в одну строчку, а написать так, чтобы потом было легко модифицировать, отлаживать и поддерживать.
Тоже далеко не всегда, если говорить про R&D. Часто необходимо написать сначала quick-and-dirty код, который никогда не будет поддерживаться для проверки гипотезы. Но понятность кода и легкость отладки остаются важными факторами.
Выпендриваться надо на примере 1 / 3 * 15. В смысле чтобы получить там строго 5, а не 4.9999999999999. Можно результат округлять, можно аналитически решать… Вариантов много. Главное — понять, что пользователь от калькулятора ждет именно 5.
Взяли?
А что если написать калькулятор, код которого содержал бы всего 1 строку, т.е. всего 1у точку с запятой не считая пакеты и импорты? Сказано — сделано.?
using System;
using System.Data;
namespace Calculator
{
class Program
{
static void Main(string[] args)
{
try { Console.WriteLine(new DataTable().Compute(string.Join(" ", args), null)); }
catch (Exception error) { Console.WriteLine(error.Message); }
}
}
}
Не так давно на хабре кто-то постил чистый C-шный код строкового калькулятора путем рекурсивного спуска (но там, понятно, не одна строка).
#include <stdio.h>
#include <stdlib.h>
double eval(char*&p);
bool is(char*&p, char c) { return *p == c ? p++, true : false; }
double un(char*&p) {
if (is(p, '(')) {
double r = eval(p);
if (!is(p, ')')) printf("expected ')'");
return r;
} else return strtod(p, &p);
}
double muls(char*&p) {
double r = un(p);
for (;;) {
if (is(p, '*')) r *= un(p);
else if (is(p, '/')) r /= un(p);
else return r;
}
}
double eval(char*&p) {
double r = muls(p);
for (;;) {
if (is(p, '+')) r += muls(p);
else if (is(p, '-')) r -= muls(p);
else return r;
}
}
int main(int argc, char** argv) {
double r = eval(argv[1]);
if (*argv[1] == 0) printf("%lf", r);
else printf("error at %s", argv[1]);
}
чистый C-шный код
В коде используется тип bool, хотя stdbool.h не подключен.
Но вот
char*&p
это же ссылка на указатель, так что это C++, а не чистый Си.
Почему-то не указан профильный хаб.
/** Хитрый трюк сказать пользователю что выражение фиговое */
try (PrintStream stream = (System.out.append("Nan"))) {}
… и потом удивляться самому "почему у меня не печатает?".
Самого главного так и не написали, в каких выражениях была сформулирована оценка этого произведения?
Это на С.
int calc(char s)
{
return strchr(s,'+')?atoi(s)+atoi(strchr(s,'+')+1):strchr(s,'-')?atoi(s)-atoi(strchr(s,'-')+1):strchr(s,'')?atoi(s)atoi(strchr(s,'')+1):strchr(s,'/')?atoi(s)/atoi(strchr(s,'/')+1):0;
}
Никаких проверок нет.
Не работает, если первое число со знаком. Можно, конечно, извратиться и обработать и этот случай. Например, использовать strrchr — тогда второе число должно быть без знака.
Знак второго числа проверять можно
strchr(strchr(s,'+')+1,'-') ||? atoi(s)-atoi(strrchr(s,'-')+1): atoi(s)+atoi(strchr(s,'+')+1)
Заменить atoi на atof для нецелых чисел.
calc("ыы5*2"); <-- ыы5 заменит на 0
Scala:
import scala.util.matching.Regex
import scala.util.matching.Regex.Groups
def calc(s: String): Unit = {
new Regex("""\s*([-+]?\d+)\s*([-+*/])\s*([-+]?\d+)\s*""")
.findFirstMatchIn(s)
.map {
case Groups(a, op, b) => (a.toDouble, op, b.toDouble)
}
.map {
case (a, "*", b) => a * b
case (a, "/", b) => a / b
case (a, "+", b) => a + b
case (a, "-", b) => a - b
}
.foreach(println)
}
(
(($input = str_replace(' ', '', $input)) !== '')
&& (str_replace([0,1,2,3,4,5,6,7,8,9,' ','+','-','*','/','.'], '', $input) === '')
&& (
($pos = strpos($input, '+')) > 0 || ($pos = strpos($input, '+', $pos + 1)) > 0
|| ($pos = strpos($input, '-')) > 0 || ($pos = strpos($input, '-', $pos + 1)) > 0
|| ($pos = strpos($input, '*')) > 0
|| ($pos = strpos($input, '/')) > 0
)
&& (($v1 = (float)substr($input, 0, $pos)) || true)
&& (($op = substr($input, $pos, 1)) || true)
&& (($v2 = (float)substr($input, $pos + 1)) || true)
&& print(
$op == '+' ? ($v1 + $v2) : (
$op == '-' ? ($v1 - $v2) : (
$op == '*' ? ($v1 * $v2) : (
$op == '/' ? ($v1 / $v2) : null
)
)
)
)
) || print('wrong expression format');
Раз уж все с регэкспами пишут, то и такой вариант добавлю, выглядит более лаконично по сравнению с некоторыми языками.
function calc($input)
{
(preg_match_all('/^([+-]?\d+(?:\.\d+)?)\s*([\+\-\*\/])\s*([+-]?\d+(?:\.\d+)?)$/', $input, $a)
&& print(
($a[2][0] == '+' ? ($a[1][0] + $a[3][0]) :
($a[2][0] == '-' ? ($a[1][0] - $a[3][0]) :
($a[2][0] == '*' ? ($a[1][0] * $a[3][0]) :
($a[2][0] == '/' ? ($a[1][0] / $a[3][0]) : null))))
)
) || print('wrong expression format');
}
Я так и не понял, в чем был смысл «теста». Проверить, умеет ли кандидат пользоваться регулярными выражениями? Так и без них там можно справиться. Я бы скорее проверил результат на устойчивость к ошибкам, которая и отличает серьезный код от студенческого, но это уже придирки.
Для серьезного теста надо было разрешить более двух аргументов. Смысл в том, что для правильного вычисления выражений 2+3*4 без приоритетов операций уже не обойтись. Даже если кандидат и не уложится в полтора часа, важно, чтобы он шел по правильному пути.
Да, можно и с функциями (из System.Math), будет на несколько строчек длиннее.
ИМХО, тут должно тестироваться не знание библиотеки, а понимание принципов обработки математических выражений. Хотя знать библиотеку тоже очень полезно: я вот не додумался до Compute…
А вот изобретать велосипеды нужно далеко не всегда, ну, и матчасть желательно знать…
//#define USE_DATATABLE
using System;
namespace Calculator
{
class Program
{
static void Main(string[] args)
{
try
{
string expression = string.Join(" ", args).ToLower();
#if USE_DATATABLE
Console.WriteLine(new System.Data.DataTable().Compute(expression, null));
#else
Console.WriteLine(Eval(FixFunctionNames(expression)));
#endif
}
catch (Exception error)
{
Console.WriteLine(error.Message);
}
}
static string FixFunctionNames(string expression)
{
foreach (System.Reflection.MethodInfo func in typeof(System.Math).GetMethods())
{
string funcName = func.GetBaseDefinition().Name.ToLower();
int funcPos = expression.IndexOf(funcName);
while (funcPos >= 0)
{
expression = expression.Insert(funcPos, "System.Math." + func.Name.Substring(0,1).ToUpper());
expression = expression.Remove(funcPos + 13, 1);
funcPos = expression.IndexOf(func.Name.ToLower(), funcPos+1);
}
}
return expression;
}
static object Eval(string expression)
{
try
{
return new Microsoft.CSharp.CSharpCodeProvider().CompileAssemblyFromSource(
new System.CodeDom.Compiler.CompilerParameters() { GenerateInMemory = true },
$"class Runtime{{public static object Eval(){{return {expression};}}}}"
).CompiledAssembly.GetType("Runtime").GetMethod("Eval").Invoke(null, null);
}
catch
{
return "Error in expression: "+expression;
}
}
}
}
Операция.Код? { 24424455, 24242566, 24244567 }
При этом я должен найти атрибут Код от объекта типа «операция» (даже если текущий объект не операция — при этом работают мои правила поиска объектов), а потом сравнить его с одним из кодов из множества. И функции у меня тоже специфические. Но runtime compile мы используем тоже, правда для других задач. А «изобретать велосипеды» — это как раз то, что у меня получается лучше всего :) Хотя я и не спорю, что знать матчасть тоже нужно.
Я разработчик С#, которому пришлось писать настоящий калькулятор. То есть, со скобками, приоритетами операций и даже с функциями.
Тогда непонятно, зачем вы упоминали калькулятор в своем посте. Код выше — это полноценный калькулятор, то есть, «со скобками, приоритетами операций и даже с функциями» :)
Так, что вы были не правы, когда говорили, «это была работа точно не на полтора часа» (ну, или правы, так как работа по написанию подобного калькулятора на C# при определенном опыте — дело 10 минут).
Что же до «смысла» такого теста, то это вопрос спорный. Скажем так: подобный тест при приеме на работу программиста начинающего/среднего уровня вполне нормальный, в принципе (правда, если не принимать во внимание, что у нас, в Штатах, обычно на собеседовании кодировать не просят; но могут попросить при screening), и уж намного лучше хитрожопых (сорри, но по иному назвать не могу!) вопросов «на IQ», или на «нестандартность мышления», или «а вот напиши мне quick sort на бумажке без компьютера», в общем, тех, которыми наши «милые» соотечественники, или нагуглившиеся индусы с китайцами любят «опускать» соискателя на собеседовании.
Скажем так: если человек достаточно быстро справился с подобной несложной, но любопытной задачкой, то, как минимум, это означает, что при решении реальной задачи, он не будет сидеть тупым анчоусом, или доставать тупыми и элементарными вопросами.
Но, опять-таки, тут все очень сильно зависит от конкретной позиции (т.е. работы).
Мне показалось, что замысел теста был в том, чтобы проверить, как кандидат САМ, без библиотек, напишет калькулятор. В этом есть немалый смысл: надо не только знать библиотеки, но и понимать, как они работают. Мы не раз требовали от кандидатов написать сортировку, и далеко не каждый с этим справился. Не вижу тут никакого «опускания». Если человек ошибся пару раз на единичку, это не страшно, а вот если он вообще не знает алгоритма — очень плохо.
Программист не должен помнить все и вся алгоритмы. Он должен уметь программировать. Хороший программист еще должен уметь искать и обрабатывать информацию. А Senior еще должен быстро входить в любую предметную область.
И, вы не поверите, но пару раз за мою более чем 20-летнюю карьеру мне все-таки пришлось писать сортировку самому.
Что значит «без библиотек»? Библиотеки, те или иные, так или иначе используются. «Без библиотек» — это значит писать на ассемблере.
Программист, который не умеет reuse code (по-русски коряво выходит, извините), негодный программист. А вот кандидат, который умеет быстро, точно и эффективно решить задачу — подходящий кандидат.
Скажем так, если бы мне пришло в голову предложить такой тест, и это было бы разрешено в компании, то человек, который сказал бы мне, что «написание такого калькулятора займет не полтора часа, а полтора месяца», вряд ли получил бы мое одобрение.
Разумеется, я не гарантирую, что код, который я напишу на бумажке, сразу будет без ошибок. Но уж алгоритмы сортировок-то я знаю. Как я уже говорил, неважно, если будут мелкие ошибки — важно, чтобы кандидат понимал суть алгоритма. Давным-давно, когда я был студентом, меня интересовали и доказательства правильности этих алгоритмов…
Я не собираюсь продолжать холивар на тему «что важнее демонстрировать на собеседовании — знание библиотек или алгоритмов». И то, и другое нужно. Но я считаю, что кандидат, плохо знающий библиотеку, обычно нагуглит недостающую информацию за пять минут, а вот кандидат, плохо знающий алгоритмы, завязнет в них надолго. И от него едва ли можно ожидать, например, выбора именно того алгоритма, который лучше всего подходит для вашей задачи.
Просьба реализовать что-то не очень сложное на интервью, хоть и необычна (для нынешних реалий), и, возможно, «оскорбительна» для претендующих на senior-ов и team lead-ов, но вот для отсеивания начинающих неумех вполне подходит. Вдобавок, позволит понять, кого вы нанимаете, и что, в дальнейшем, вам ждать от человека.
Еще раз повторю, что, даже если бы вы нарисовали мне полностью рабочий quicksort на бумажке по памяти, один к одному из книжки Вирта, и рассказали бы даже то, чего я не знаю и не слышал никогда, но в ответ на просьбу написать калькулятор, заявили бы, что это задача на месяц, то работу вы бы не получили. Пусть я 100 раз не прав и «придираюсь», и вы немеряно круты и за год можете написать и отладить лексер с рекурсивным спуском.
fun calc(expression: String): BigDecimal {
return "([-+]?\\d+)\\s*([+\\-*/])\\s*([-+]?\\d+)"
.toRegex()
.find(expression)
?.destructured
?.let { (left, op, right) ->
when (op) {
"+" -> BigDecimal::plus
"-" -> BigDecimal::minus
"*" -> BigDecimal::times
"/" -> BigDecimal::div
else -> throw IllegalArgumentException()
}(BigDecimal(left), BigDecimal(right))
} ?: throw IllegalArgumentException()
}
fun main(args: Array<String>) {
require(calc("+5 + -12") == BigDecimal(5 - 12))
require(calc("+5 * -12") == BigDecimal(5 * -12))
require(calc("+5 - -12") == BigDecimal(5 - (-12)))
require(calc("+5 / -12") == BigDecimal(5).div(BigDecimal(-12)))
}
fun calc(expression: String): BigDecimal {
val ast = simpleParser().parse(StringReader(expression))
return ast?.evaluate(EmptyEvaluationContext()) ?: throw IllegalArgumentException()
}
fun main(args: Array<String>) {
require(calc("(5 + -14) - 23") == BigDecimal((5 + (-14)) - 23))
require(calc("((5 + -14) - 23) + (-10 * 6)") == BigDecimal(((5 + -14) - 23) + (-10 * 6)))
}
static void Main(string[] args)
{
Console.WriteLine(Eval("(5 * 5) - 2"));
}
static object Eval(string expression)
{
return new CSharpCodeProvider().CompileAssemblyFromSource(
new CompilerParameters() { GenerateInMemory = true },
$"class Runtime{{public static object Eval(){{return {expression};}}}}"
).CompiledAssembly.GetType("Runtime").GetMethod("Eval").Invoke(null, null);
}
double calc(const char * s, char c = '+', double a = 0.0, double b = 0.0)
{
return (sscanf(s,"%lf %c %lf",&a,&c,&b) == 3) ? ((c == '+') ? (a+b) : (c == '-') ? (a-b) : (c == '*') ? (a*b) : (a/b)) : 0.0;
}
int main()
{
cout << calc("-3.154+18") << endl;
cout << calc("-3.154 +-18") << endl;
}
Мы не на соревновании.
Кажется, в комментариях слишком мало странных вариантов c очень ограниченной функциональностью, написанных с использованием JS.
(s=>!(document.body.style.width='calc('+s.replace(/(\d+)/g,'$&px')+')')||document.body.offsetWidth)('5 + 45');
public static void main(String[] args) {
System.out.println(calc("+5 + -12").equals("-7.0"));
System.out.println(calc("+5 * -12").equals("-60.0"));
System.out.println(calc("+5 - -12").equals("17.0"));
System.out.println(calc("+5 / -12").equals("-0.4166666666666667"));
System.out.println(calc("+5 & -12").equals("unknown operator"));
System.out.println(calc("-12").equals("invalid expression"));
}
private static String calc(String expr) {
return expr.split(" ").length > 2 ?
expr.split(" ")[1].equals("+") ? "" + (Double.valueOf(expr.split(" ")[0]) + Double.valueOf(expr.split(" ")[2])) :
expr.split(" ")[1].equals("-") ? "" + (Double.valueOf(expr.split(" ")[0]) - Double.valueOf(expr.split(" ")[2])) :
expr.split(" ")[1].equals("*") ? "" + (Double.valueOf(expr.split(" ")[0]) * Double.valueOf(expr.split(" ")[2])) :
expr.split(" ")[1].equals("/") ? "" + (Double.valueOf(expr.split(" ")[0]) / Double.valueOf(expr.split(" ")[2])) : "unknown operator" : "invalid expression";
}
F# но не поддерживает отрицаетльные числа
open System.Text.RegularExpressions
let calc s =
Regex.Split(s, @"\s*([\d\.]+)\s*")
|> Seq.chunkBySize 2
|> Seq.filter (Array.length >> (=) 2)
|> Seq.fold (fun acc [|op; no|] ->
([("-", (-)); ("+", (+)); ("*", (*)); ("/", (/)); ("", (+))] |> Map.ofSeq |> Map.find op) acc (float no)
) 0.
[<EntryPoint>]
let main argv =
printfn "%A" (calc "2+2")
printfn "%A" (calc "2*3.5 + 8")
0 // возвращение целочисленного кода выхода
using System;
using System.Linq;
using System.Globalization;
using static System.Console;
class Program
{
static double Calc(string expr) =>
expr.Split("+-*/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
.Select(x => double.Parse(x, CultureInfo.GetCultureInfo("en")))
.Aggregate(double.NaN, (a, e) =>
expr.Where(x => "+-*/".Contains(x)).First() switch
{
'+' => double.IsNaN(a) ? e : a + e,
'*' => double.IsNaN(a) ? e : a * e,
'-' => double.IsNaN(a) ? e : a - e,
'/' => double.IsNaN(a) ? e : a / e,
_ => double.NaN
});
static void Main(string[] args)
{
WriteLine(Calc("2+2"));
WriteLine(Calc("2*3"));
WriteLine(Calc("12/2.5"));
WriteLine(Calc("13.4-7.2"));
ReadLine();
}
}
Однострочный калькулятор, искусство или порок?