Вводная
Как это часто бывает, когда Вы ищете работу, Вы проходите одно собеседование за другим. Где-то Вас выбирают, где-то Вы. И наверное, в жизни каждого из нас бывали интересные собеседования, о которых можно с удовольствием поведать публике. Я хочу рассказать об одной такой истории, где есть место эмоциям, панике, потоку
История одного человека X
В далеком 2008 году, парень по имени X искал работу программистом. Опыт разработки у него был, но не такой, когда отрывают с руками и ногами. Поэтому он отвлекался на все вакансии и отвечал на все звонки. И вот свершилось, X'а пригласили в серьезную компанию, где ему предстояло пройти всего 2 собеседования. Одно, как это водится, с девчатами из отдела кадров, которое впрочем помехой не стало, а второе — техническое, волнительное, сердце тревожное, неизвестное. Настал час X — собеседование. После принятого рукопожатия, молодой человек, по имени Y, интервьюер, истинный программист — прическа в бок, джинса подстёрлась, сказал, что очень занят сейчас. Ну раз ты пришел, так и быть, дам тебе хорошенький ноутбук и задачку — 'Напиши мне калькулятор. Простой калькулятор, когда на вход программе подается выражение, состоящее из 2 чисел, разделенные знаком '+', '-', '*', '/', которое нужно посчитать. У тебя полтора часа.'. И в тот момент, произошло нечто важное — 'Удиви меня!', надменно добавил он и ушел. В эту секунду человека X накрыл шквал негативных эмоций — 'Ага, ща. Достам АКС 74, 5.45 и заставлю тебя танцевать лезгинку и напевать Надежду Бабкину — 'Виновата ли я'. Во диву то будет, танцуй сколько хошь… '.
Но эмоции на то и эмоции, чтобы уступать место здравому смыслу. Грубость — не аргумент. Процесс пошел, мысли забурлили: а может вызвать calc.exe, а может ООП навернуть, а может офигенный парсер выражения сделать. Но нет. Всего полтора часа. Может просто сделать задачу? Как поступить? Путь был выбран — 'Сделаю как смогу и точка с запятой. Ох уж и постановочка, ох уж и собеседование'. Минут через 20 на лице X'а появилась улыбка. Его осенило! А что если написать калькулятор, код которого содержал бы всего 1 строку, т.е. всего 1у точку с запятой не считая пакеты и импорты? Сказано — сделано. К концу второго часа решение было готово. 2 часа. Пришел уставший и немного замороченный Y. 'Ну как?' — спросил он X'а. 'Готово!' — ответил тот.
Интересный и мозговзрывательный код на java
Итак, дорогой читатель пришло время и нам с Вами попробовать решить поставленную, человеку X, задачку. Вот более точная формулировка задания: Необходимо написать калькулятор для простого выражения, который бы содержал ровно 1 строчку кода и умел складывать, вычитать, умножать и делить 2 числа. 1 строчка — означает ровно 1 точку с запятой, исключая декларацию пакета и импортов. Для решения можно использовать только классы из jdk. Примеры выражения «7 + 4», «-12.0 * 14.12» без скобок и каких либо хитростей. Решение нужно оформить в 1 статическом методе, выводящего в консоль результат. Функцию main не трогать — в ней будут вызываться функции для проверки результатов. С ограничениями, пожалуй все. Любые трюки приветствуются. Оригинальность тоже.
Варианты
В java 7 это делается довольно просто и тут не нужно быть гением. Пожертвуем немного точностью и безопасностью. Класс буду приводить полностью. Если хотите подумать жать на спойлер не обязательно.
package com.calculator;
import javax.script.ScriptEngineManager;
import java.io.PrintStream;
public class Calculator1 {
/**
* Самый простой, но менее точный результат. Что с безопасностью?
* @param expression выражение для расчета
*/
private static void calc(String expression) {
try {
System.out.println(new ScriptEngineManager().getEngineByName("JavaScript").eval(expression));
} catch (Exception ex) {
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");
}
}
В 2008 году такой трюк бы не прошел, поэтому человек X решил эту задачу по своему. Примечание: код все равно адаптирован под java 7, уж простите.
package com.calculator;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Calculator2 {
/**
* Вариант, который будет предложен в том или ином виде большинством
* @param expression выражение для расчета
* @param args хитрость, до которой стоит догадаться
*/
private static void calc(String expression, Object ... args) {
try {
// 1. Отображаем результат
System.out.println(
// 2. Ищем метод по коду операции
BigDecimal.class.getMethod(
Arrays.asList("multiply", "add", "subtract", "divide").get(
((args = new Matcher[] {
Pattern.compile(
// 3. Регулярка для анализа выражения. Отмечу, что регулярка то и <b>не очень важна</b>, ее можно допилить так как хотите.
"[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\s*([+-\\*/])\s*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$")
.matcher(expression)})) != null &&
((Matcher) args[0]).find() ?
// 4. Коды символов основных операций 42: '*', 43: '+', 45: '-', 47: '/' - простая формула дает индексы 0, 1, 2, 3
((int) ((Matcher) args[0]).group(1).charAt(0) - 41) / 2 : -1),
// 5. Вычисляем результат
BigDecimal.class, MathContext.class).invoke(
// 6. Первый аргумент пошел
new BigDecimal(((args = new Matcher[] {
Pattern.compile("([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)").matcher(expression)})) != null &&
((Matcher) args[0]).find() ? ((Matcher) args[0]).group(0) : ""),
// 7. Второй аргумент пошел
new BigDecimal(((args = new Matcher[] {
Pattern.compile("[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\s*[+-\\*/]\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)$")
.matcher(expression)})) != null && ((Matcher) args[0]) .find() ? ((Matcher) args[0]).group(1) : ""), new MathContext(10, RoundingMode.HALF_EVEN)));
} catch (Exception ex) {
/** Хитрый трюк сказать пользователю что выражение фиговое */
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");
}
}
Как в известной песни: Ну что сказать, ну что сказать, устроена так java, желают знать, желают знать, желают знать, что будет…
Но признаемся себе, этот код слишком громоздкий и имеет повторения. А что если усилить ограничение и потребовать не использовать тернарный оператор вовсе? Не сразу, но решение все же нашлось.
package com.calculator;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Calculator3 {
/**
* Вариант, без тернарного оператора, здесь нужно немного подумать.
* @param expression выражение для расчета
* @param args хитрость, до которой стоит догадаться
*/
private static void calc(String expression, Object ... args) {
try {
// 1. Отображаем результат
System.out.println(
// 2. Ищем метод по коду операции
BigDecimal.class.getMethod(
Arrays.asList("multiply", "add", "subtract", "divide").get(
// 3. Запоминаем все требуемые значения в args и достаем код операции
(Integer) (args = new Object[] {args = new Object[] {
Pattern.compile("([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)\s*([+-\\*/])\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)$").
matcher(expression)}, args[0], ((Matcher) args[0]).find(), ((Matcher) args[0]).group(1), ((int) ((Matcher) args[0]).group(2).charAt(0) -41) / 2,
((Matcher) args[0]).group(3)})[4]),
// 4. Вычисляем результат
BigDecimal.class, MathContext.class).invoke(
// 5. Первый аргумент пошел
new BigDecimal(args[3].toString()),
// 6. Второй аргумент пошел
new BigDecimal(args[5].toString()), new MathContext(10, RoundingMode.HALF_EVEN)));
} catch (Exception ex) {
/** Хитрый трюк сказать пользователю что выражение фиговое */
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");
}
}
Так гораздо короче и без повторений, но
Интересно, а как думают парни, излагающие свои мысли на scala или kotlin или c# или ..., если указанные ограничения пусть и с допущениями — подходят?
Заключение
Спасибо дорогой читатель, за твое внимание и терпение. Как и обещал даю свой ответ на поставленный вопрос: 'Необычный код — искусство или порок?'. Я бы сказал так: 'Глазами экспериментатора — исскуство, глазами продакшена — порок'. Но как бы такой код не называли, помни, ты можешь попробовать. Отдельно хочу извиниться перед жителями habrahabr за выбранный стиль изложения, если что не так. Это экспериментальный с моей стороны подход, спасибо за понимание.
Автор: reforms