Как стать автором
Обновить

Однострочный калькулятор, искусство или порок?

Время на прочтение6 мин
Количество просмотров45K

Вводная


Как это часто бывает, когда Вы ищете работу, Вы проходите одно собеседование за другим. Где-то Вас выбирают, где-то Вы. И наверное, в жизни каждого из нас бывали интересные собеседования, о которых можно с удовольствием поведать публике. Я хочу рассказать об одной такой истории, где есть место эмоциям, панике, потоку мышления и вдохновению. Речь в статье пойдет о внутренних переживаниях соискателя, о его противостоянии с интервьюером, интересный и мозговзрывательный код на java, а также ответ на поставленный вопрос: 'Необычный код — искусство или порок?'. Вы сможете окунуться в свое прошлое и размять мозги. Если заинтриговал, тогда поехали.

История одного человека 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 за выбранный стиль изложения, если что не так. Это экспериментальный с моей стороны подход, спасибо за понимание.
Теги:
Хабы:
+13
Комментарии105

Публикации

Изменить настройки темы

Истории

Работа

Java разработчик
356 вакансий
Scala разработчик
21 вакансия

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн