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

в 18:02, , рубрики: java, kotlin, scala, Искусство или порок, калькулятор, Мозговзрывательный, метки: , ,

Вводная

Как это часто бывает, когда Вы ищете работу, Вы проходите одно собеседование за другим. Где-то Вас выбирают, где-то Вы. И наверное, в жизни каждого из нас бывали интересные собеседования, о которых можно с удовольствием поведать публике. Я хочу рассказать об одной такой истории, где есть место эмоциям, панике, потоку мышления и вдохновению. Речь в статье пойдет о внутренних переживаниях соискателя, о его противостоянии с интервьюером, интересный и мозговзрывательный код на 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 за выбранный стиль изложения, если что не так. Это экспериментальный с моей стороны подход, спасибо за понимание.

Автор: reforms

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js