Страницы

Поиск по вопросам

воскресенье, 30 сентября 2018 г.

Код-гольф - Реализация алгоритма выборки комбинаций

Приём ответов завершён, всем спасибо за участие! Можете оставлять свои решения, но победители уже выбраны и пересчёта не будет.

Приветствую.
Задача: Напишите функцию, которая из произвольного входящего массива выберет все комбинации чисел, сумма которых будет равняться 10.
Подробнее: Диапазон чисел: 0 - 1000 включительно. Количество чисел во входящем массиве: 1 - 10 включительно. Уже выбранные числа могут использоваться неоднократно, но комбинации должны быть уникальны. --- Смена позиций не делает комбинацию уникальной, т. е. [1,9] и [9,1] не позволяются). --- При входных данных [5,5,2,3] можно сделать [5,5], [5,2,3] Продолжительность конкурса: 14 дней Обязательный формат метки ответа (для автоматического парсера в таблицу):

Язык, КоличествоСимволов


Определение победителя: Определяется по градации:
Реализация, состоящая из наименьшего количества символов. Реализация, получившая больше всего плюсов (общий рейтинг минус голоса против). Реализация, время первой редакции которой раньше.
Необходимо так же давать ссылку на один из онлайн-компиляторов с Вашим рабочим кодом. Тесты приветствуются. Настоятельно рекомендуется давать как конкурсную версию кода (минифицированную и/или с колдунствами), так и наглядное описание этого же кода. Всем интересно не только увидеть языковую магию, но и что-то почерпнуть для себя.
От одного участника допустимо несколько ответов, если они реализованы на разных языках. Но не более одного на язык

Все вопросы по деталям и/или разногласиям и/или трактовке условий, прошу обсуждать в комментариях к этому сообщению или в комнате чата по код-гольфу

Итоги:
Стандартные 3 места + 1 за самое большое количество плюсов (общий рейтинг минус голоса против).
1 место: @PavelMayorov - Haskell, 42 символа 2 место: @ArtemKonovalov - Scala, 69 символов 3 место: @Mike - Perl, 78 символов
Зрительские симпатии: @D-side (Ruby, 84) - 19 баллов
Поздравления всем участникам, вы хорошо сражались. Особенно страсти накалились в самом конце, когда неожиданно было опубликовано решение обгоняющее лидера на 1 символ. Но судьба вернула всё обратно.
Хотелось бы отметить необычное для данного соревнования решение от пользователя @AlexanderGavrikov - 1437 символов! Это своеобразный рекорд, стоит отметить.

Таблица лидеров:
execute(581668); .cssload-container,.cssload-cube{width:97px;height:97px;transform-style:preserve-3d}.cssload-container,.cssload-cube,.cssload-half1,.cssload-half2{transform-style:preserve-3d}.cssload-container{position:relative;margin:23px 84px;perspective:292px}.cssload-cube{animation:cube 11.5s forwards infinite;transform-origin:center 49px}.cssload-half1,.cssload-s1{top:0;transform-origin:50% 100%}.cssload-half1{height:39px;position:absolute;animation:half-fold 11.5s forwards infinite}.cssload-side{width:19px;height:19px;background:#ddd;position:absolute}.cssload-s1{left:39px;animation:s1ani 11.5s forwards infinite}.cssload-s2,.cssload-s3,.cssload-s4{left:39px;transform-origin:50% 0}.cssload-s2{top:19px;animation:s2ani 11.5s forwards infinite}.cssload-s3{top:39px;animation:s3ani 11.5s forwards infinite}.cssload-s4{top:58px;animation:s4ani 11.5s forwards infinite}.cssload-s5{left:19px;top:19px;transform-origin:100% 50%;animation:s5ani 11.5s forwards infinite}.cssload-s6{left:58px;top:39px;transform-origin:0 50%;animation:s6ani 11.5s forwards infinite}@keyframes cube{0%,30%{transform:rotateX(0)}40%{transform:rotateX(45deg) rotateY(0) rotate(45deg)}60%{transform:rotateX(60deg) rotateY(0) rotate(45deg)}65%,70%{transform:rotateX(60deg) rotate(45deg) rotate(180deg)}75%,80%{transform:rotateX(60deg) rotate(45deg) rotate(1turn)}90%{transform:rotateX(0) rotate(0) rotate(0)}}@keyframes s1ani{0%{opacity:1;transform:translateY(0);background:#ddd}40%{transform:rotateX(0);background:#ddd}50%{transform:rotateX(-90deg);background:#ddd}90%{transform:rotateX(-90deg)}}@keyframes s2ani{0%{opacity:0;transform:rotateX(-179deg)}10%{opacity:1;transform:rotateX(0)}40%{background:#ddd}45%,80%{background:#b4b4b4}65%{opacity:1;background:#b4b4b4}90%{opacity:1}to{opacity:0}}@keyframes s3ani{0%,10%{opacity:0;transform:rotateX(-179deg)}20%,90%{opacity:1;transform:rotateX(0)}40%{background:#ddd}45%{background:#969696}to{opacity:0}}@keyframes s4ani{0%,20%{opacity:0;transform:rotateX(-179deg)}10%,to{opacity:0}30%{opacity:1;transform:rotateX(0)}40%{transform:rotateX(0);background:#ddd}50%{transform:rotateX(90deg);background:#b4b4b4}80%{background:#b4b4b4}90%{opacity:1;transform:rotateX(90deg)}}@keyframes s5ani{0%,10%{opacity:0;transform:rotateY(-179deg)}20%{opacity:1;background:#ddd;transform:rotateY(0)}40%{transform:rotateY(0)}50%{transform:rotateY(90deg)}55%{background:#ddd}60%{background:#c8c8c8}90%{transform:rotateY(90deg);opacity:1}to{opacity:0}}@keyframes s6ani{0%,20%{opacity:0;transform:rotateY(179deg)}30%{opacity:1;transform:rotateY(0)}40%{transform:rotateY(0)}50%{transform:rotateY(-90deg);background:#ddd}60%,80%{background:#c8c8c8}90%{opacity:1;transform:rotateY(-90deg)}to{opacity:0}}@keyframes half-fold{0%,50%{transform:rotateX(0)}60%,90%{transform:rotateX(-90deg)}} .cssload-container,.cssload-cube{width:97px;height:97px;transform-style:preserve-3d}.cssload-container,.cssload-cube,.cssload-half1,.cssload-half2{transform-style:preserve-3d}.cssload-container{position:relative;margin:23px 84px;perspective:292px}.cssload-cube{animation:cube 11.5s forwards infinite;transform-origin:center 49px}.cssload-half1,.cssload-s1{top:0;transform-origin:50% 100%}.cssload-half1{height:39px;position:absolute;animation:half-fold 11.5s forwards infinite}.cssload-side{width:19px;height:19px;background:#ddd;position:absolute}.cssload-s1{left:39px;animation:s1ani 11.5s forwards infinite}.cssload-s2,.cssload-s3,.cssload-s4{left:39px;transform-origin:50% 0}.cssload-s2{top:19px;animation:s2ani 11.5s forwards infinite}.cssload-s3{top:39px;animation:s3ani 11.5s forwards infinite}.cssload-s4{top:58px;animation:s4ani 11.5s forwards infinite}.cssload-s5{left:19px;top:19px;transform-origin:100% 50%;animation:s5ani 11.5s forwards infinite}.cssload-s6{left:58px;top:39px;transform-origin:0 50%;animation:s6ani 11.5s forwards infinite}@keyframes cube{0%,30%{transform:rotateX(0)}40%{transform:rotateX(45deg) rotateY(0) rotate(45deg)}60%{transform:rotateX(60deg) rotateY(0) rotate(45deg)}65%,70%{transform:rotateX(60deg) rotate(45deg) rotate(180deg)}75%,80%{transform:rotateX(60deg) rotate(45deg) rotate(1turn)}90%{transform:rotateX(0) rotate(0) rotate(0)}}@keyframes s1ani{0%{opacity:1;transform:translateY(0);background:#ddd}40%{transform:rotateX(0);background:#ddd}50%{transform:rotateX(-90deg);background:#ddd}90%{transform:rotateX(-90deg)}}@keyframes s2ani{0%{opacity:0;transform:rotateX(-179deg)}10%{opacity:1;transform:rotateX(0)}40%{background:#ddd}45%,80%{background:#b4b4b4}65%{opacity:1;background:#b4b4b4}90%{opacity:1}to{opacity:0}}@keyframes s3ani{0%,10%{opacity:0;transform:rotateX(-179deg)}20%,90%{opacity:1;transform:rotateX(0)}40%{background:#ddd}45%{background:#969696}to{opacity:0}}@keyframes s4ani{0%,20%{opacity:0;transform:rotateX(-179deg)}10%,to{opacity:0}30%{opacity:1;transform:rotateX(0)}40%{transform:rotateX(0);background:#ddd}50%{transform:rotateX(90deg);background:#b4b4b4}80%{background:#b4b4b4}90%{opacity:1;transform:rotateX(90deg)}}@keyframes s5ani{0%,10%{opacity:0;transform:rotateY(-179deg)}20%{opacity:1;background:#ddd;transform:rotateY(0)}40%{transform:rotateY(0)}50%{transform:rotateY(90deg)}55%{background:#ddd}60%{background:#c8c8c8}90%{transform:rotateY(90deg);opacity:1}to{opacity:0}}@keyframes s6ani{0%,20%{opacity:0;transform:rotateY(179deg)}30%{opacity:1;transform:rotateY(0)}40%{transform:rotateY(0)}50%{transform:rotateY(-90deg);background:#ddd}60%,80%{background:#c8c8c8}90%{opacity:1;transform:rotateY(-90deg)}to{opacity:0}}@keyframes half-fold{0%,50%{transform:rotateX(0)}60%,90%{transform:rotateX(-90deg)}} /* TODO: Fix it */ body { font-size: 1rem; line-height: 1.5rem; font-family: 'Open Sans', sans-serif; background: #fff; padding: 0 2rem; } h1 { font-weight: 600; margin-bottom: 3rem; text-align: center; color: #212121; } #leadership { width: 100%; margin: 1rem auto; border-collapse: collapse; box-shadow: 0 2px 7px rgba(0,0,0,0.2); background: #fafafa; } #leadership td { padding: 1rem .5rem !important; text-align: left; font-weight: 500; transition: all .3s ease-in-out; } #leadership tr:hover td{ background: #03a9f4; color: #fefefe; } #leadership tr:hover td a { color: #fff; } #leadership th { padding: 1.5rem .5rem !important; color: #727272; text-align: left !important; font-weight: 500; border-bottom: 1px solid #dcdcdc; } #leadership a { text-decoration: none; color: #212121; } #leadership a:hover { color: #03a9f4; } #leadership td:nth-of-type(1){ text-align: center; color: #727272; font-size: .75rem; } #leadership td:nth-of-type(2){ } #leadership td:nth-of-type(2) img { width: 34px; border-radius: 50%; } /* #leadership th:nth-of-type(1), #leadership th:nth-of-type(2){ border-bottom: none; } */ #leadership th:nth-of-type(5), #leadership th:nth-of-type(6), #leadership th:nth-of-type(7), #leadership td:nth-of-type(5), #leadership td:nth-of-type(6), #leadership td:nth-of-type(7) { text-align: center !important; }

Удачи всем!


Ответ

Haskell, 42 70 символа
g=nub.filter((10==).sum).subsequences.sort
Требует импорта функций nub, sort и subsequences из Data.List
Запуск: main = print $ g [5,5,2,3] http://ideone.com/5jUIsy
Как работает:
входной список сортируется (иначе функция nub, см. далее, не поймет); функция subsequences находит все сочетания (без ограничения длины), их 2m, где m - длина входного списка; при помощи filter выбираются те сочетания, которые дают нужную сумму; при помощи nub выбираются различные сочетания.
PS спасибо участнику Artem Konovalov за то, что опосредованно подсказал мне не писать велосипед, а заглянуть в стандартную библиотеку языка :)

Отображение числа 9223372036854775807

Почему разные языки по-разному отображают число 9223372036854775807, хотя все используют один и тот же формат 8-байтного double для представления чисел?
9223372036854775807 - в коде 9223372036854775808 - C++ http://ideone.com/PV5iPg и http://codepad.org/vhQzDMqT 9223372036854776000 - Javascript https://jsfiddle.net/5ugL4rqh/ 9223372036854776000 - Java http://ideone.com/QtXRWi 9223372036854780000 - C# http://ideone.com/36Lzzi


Ответ

Здесь в каждой среде/языке два преобразования:
Из константы в исходном коде в объект в памяти Печать этого объекта памяти выбранным способом.
C++
Эффект от кода из вопроса для С++: volatile double x = 9223372036854775807.; схож с gcc's -ffloat-store опцией и позволяет забыть о возможных дополнительных битах и думать только о 64-битных IEEE 754 числах двойной точности, используемые в рассматриваемой реализации (IEEE 754 не обязателен, но конкретная реализация для float чисел должна быть задокументирована).
Константа 9223372036854775807. из исходного кода превращается в 9223372036854775808. double (ожидаемо для этого типа, см. демонстрацию битового представления внизу). В CPython тоже самое происходит:

>>> 9223372036854775807. .as_integer_ratio() (9223372036854775808, 1) >>> 9223372036854775807. .hex() '0x1.0000000000000p+63'
то есть 9223372036854775807. не может быть точно представлено в IEEE 754 double и поэтому используется приближение 9223372036854775808. (263), которое уже выводится точно в этом случае с помощью: cout << fixed << x; как ascii-строка: "9223372036854775808.000000" (в C локали).
Как double в памяти и в виде бит в IEEE 754 представлен, и как печать может происходить в С, подробно описано в ответе на вопрос printf как средство печати переменных в С
В данном случае, так как число является степенью двойки, то легко найти его IEEE 754 представление:
d = ±знак · (1 + мантисса / 252) · 2порядок − 1023
знаковый бит равен нулю, так как число положительное порядок = (63 + 1023)10 = 100001111102, чтобы получить 263 у мантисса все явные 52 бита нулевые (старший неявный 53ий бит всегда равен единице)
Все биты числа вместе:
0 10000111110 0000000000000000000000000000000000000000000000000000
Что подтверждается вычислениями на Питоне:
>>> import struct >>> struct.pack('>d', 9223372036854775808.0).hex() 43e0000000000000 >>> bin(struct.unpack('>Q', struct.pack('>d', 9223372036854775808.0))[0])[2:].zfill(64) '0100001111100000000000000000000000000000000000000000000000000000'
И в обратную сторону:
>>> b64 = 0b0_10000111110_0000000000000000000000000000000000000000000000000000 .to_bytes(8, 'big') >>> b64.hex() '43e0000000000000' >>> "%f" % struct.unpack('>d', b64)[0] '9223372036854775808.000000'
Порядок байт в памяти у числа в примере показан от старшего к младшему (big-endian), но фактически может быть и от младшего к старшему (little-endian):
>>> struct.pack('d', 9223372036854775808.0).hex() '000000000000e043'
Можно посмотреть, что не подряд идут представимые числа, вычитая/прибавляя по одному биту к мантиссе:
>>> x = 0b0_10000111110_0000000000000000000000000000000000000000000000000000
>>> def to_float_string(bits): ... return "%f" % struct.unpack('>d', bits.to_bytes(8, 'big'))[0]
>>> for n in range(x-1, x+2): ... print(to_float_string(n)) 9223372036854774784.000000 9223372036854775808.000000 9223372036854777856.000000
Разница в один бит для чисел этой величины ведёт к разнице больше тысячи в десятичном представлении: ..4784, ..5808, ..7856
Можно воспользоваться C99 функцией nextafter()
#include #include #include
int main(void) { volatile double x = 9223372036854775808.0; printf("%f
", nextafter(x, DBL_MIN)); printf("%f
", x); printf("%f
", nextafter(x, DBL_MAX)); }
Результаты совпадают с предыдущими:
9223372036854774784.000000 9223372036854775808.000000 9223372036854777856.000000
Javascript
Числа в JavaScript представлены интересным способом — целые как IEEE 754 double представлены. К примеру, максимальное число (Number.MAX_SAFE_INTEGER) равно 253
9223372036854775807 на три порядка больше MAX_SAFE_INTEGER поэтому нет гарантии, что n и n+1 представимы.
> 9223372036854776000 === 9223372036854775807 true > 9223372036854775808 === 9223372036854775807 true
9223372036854776000 (результат document.write(9223372036854775807) в одной из javascript реализаций) допустим cтандартом в качестве строкового представления для 9223372036854775807 (это по-прежнему одно binary64 число: 0x1.0000000000000p+63).
Результаты побитовых операций вообще ограничены 32-битными числами со знаком. Можно посмотреть на какие ухищрения пришлость пойти, чтобы воспроизвести результат хэш-функции, реализованной в javascript: Как перевести из Javascript в Питон функцию хэширования строки
Java
В Java, double это тип со значениями, которые включают 64-bit IEEE 754 числа с плавающей точкой.
double x = 9223372036854775807.; System.out.format("%f", x);
Возможная логика, почему 9223372036854776000, а не 9223372036854775808. десятичное представление выбрано для binary64 числа 0x1.0000000000000p+63 в том, что в общем случае это позволяет меньше цифр печатать для дробных чисел — не отображаются завершающие нули (это спекуляция — я не углублялся в этот вопрос).
C#
msdn утверждает что double в C# соотвествует IEEE 754.
9223372036854780000.0 намекает, что Console.WriteLine("{0:0.0}", x); округляет до 15 цифр при печати. Напечатанное число отличается от x
>>> 9223372036854780000. .hex() '0x1.0000000000002p+63'
Вероятно это происходит по cхожей причине, что и 0.1 показывается как 0.1 при печати, а не 0.10000000000000001 или вообще 0.1000000000000000055511151231257827021181583404541015625 ( 0.1 .hex() == '0x1.999999999999ap-4'). Более того binary64 представление другое (263 vs. 263+212) (единственный из представленных примеров в вопросе, который по умолчанию не выводит эквивалентное представление).
Возможно, приоритет в округлении до 15 цифр, не обращая внимание достаточно ли это, чтобы эквивалентное binary64 представление получить. Не только C# себя так ведёт, к примеру, numpy.array в Питоне выводит по умолчанию 8 цифр:
>>> import numpy >>> a = numpy.array([2**63-1, 2**63, 2**63+2**12], dtype=numpy.float64) >>> a array([ 9.22337204e+18, 9.22337204e+18, 9.22337204e+18]) >>> 9.22337204e+18 .hex() '0x1.0000000176f0ap+63' >>> numpy.set_printoptions(precision=16) >>> a array([ 9.2233720368547758e+18, 9.2233720368547758e+18, 9.2233720368547799e+18])
Показ 17 цифр в C# возможен с помощью стандартного "{0:R}" формата, что выводит 9.2233720368547758E+18, то есть снова ту же рассматриваемую изначальную 263 степень получили:
>>> 9.2233720368547758E+18 .hex() '0x1.0000000000000p+63'

Перевести программу с Паскаля на [любой-язык]

Конкурс окончен, смотрите результаты в конце вопроса.

Я думаю, многие из вас видели вопросы, состоящие из просьбы перевода программы с одного языка на другой. Давайте-ка покажем, как делать такие вещи правильно! В нашем конкурсе мы исходим из такой простой программы на Паскале:
program test; var a, b, c: integer; begin readln(a); readln(b); c := a + b; writeln(c); end.
(Она же на ideone.)
Задание состоит в следующем: вы должны перевести программу на любой-язык так, чтобы сохранить как можно больше от исходного текста программы. В качестве целевого языка, понятно, исключаются языки группы Паскаля (Delphi, Algol, Oberon, Modula, etc., все языки, в которых используется begin/end для группировки команд в составную команду).
Вы можете дописывать конструкции до и после данного в условии текста, но не внутри его (точнее, можете и внутри, но это будет считаться изменением — смотрите ниже условия подсчёта). Сам текст желательно менять как можно меньше.
Ограничения: Строки исходной программы между begin и end должны сохранять свой смысл. Они должны выполняться, и при их выполнении должно происходить в общих чертах то же, что и в исходной программе: readln должно считывать значения с консоли, c := a + b должно складывать значения двух переменных (или что там есть в вашем языке) в третью, writeln должно выводить правильный результат на консоль. (Это означает, что вы не можете просто закомментировать код первоначальной программы.)
Определение победителя: Выигрывает код, в котором исходный текст менее всего изменён по сравнению с полным первоначальным вариантом (количество добавленных символов + количество удалённых символов + количество изменённых символов), считая и строки с program и end.. Разница в больших/маленьких буквах, а также замена символа на одинаковый по начертанию (например, русское «с»/английское «c») считается за пол-символа. Если два решения имеют одинаковое количество отличий (например, ноль), выигрывает то, у которого меньше добавленного кода (в символах). Если несколько решений одинаковы и по этому критерию, выигрывает то решение, которое получит больше голосов (как обычно, «за» минус «против»).
В частности, полное совпадение кода выигрывает у неполного независимо от количества подготовительного кода.
Для того, чтобы было легче проверять ваш код, старайтесь публиковать ссылку на онлайн-компилятор с вашим кодом. Код должен компилироваться без ошибок (пусть даже с предупреждениями) и правильно работать в диапазоне входных чисел от 0 до 1000.
Продолжительность конкурса — 1 неделя.

Для исключения разночтений, при неясности в правилах пожалуйста переспрашивайте в комментариях или в чате, посвящённом code golf

Для примера, вот внеконкурсное решение на plain TeX:

ewcount\tmp
ewcount\c
\def\uncatcodeletters{\uncatcoderange{`a}{`z}\uncatcoderange{`A}{`Z}} \def\uncatcoderange#1#2{% \tmp=#1 \advance\tmp -1 \loop\advance\tmp 1 \expandafter\catcode
umber\tmp=11 \ifnum\tmp<#2
epeat}
\def\s{\begingroup\uncatcodeletters\shlp} {\def~ := #1 + #2;{% \global\expandafter\c\csname #1\endcsname \global\expandafter\advance\expandafter\c\csname #2\endcsname \endgroup} \global\let\shlp=~}
\def\inp{\begingroup\uncatcodeletters\inphelper} \def\inphelper eadln(#1);{\endlinechar=-1 \escapechar=-1 \expandafter\inphelperi\csname #1\endcsname\endgroup} \def\inphelperi{\global
ead16 to }
\def\out{\begingroup\uncatcodeletters\outhelper} \def\outhelper riteln(#1);{\message{\expandafter\the\csname #1\endcsname}\endgroup}
\let\DEF\def \let\END\end \catcode`r=13 \let r=\inp \catcode`w=13 \let w=\out \catcode`p=14 \catcode`v=14 \catcode`b=14 \let~=\catcode ~`c=13 \let c=\s ~`e=13 \DEF end.{\END}
program test; var a, b, c: integer; begin readln(a); readln(b); c := a + b; writeln(c); end.
(Если кому интересно, гольфированный вариант.) Транскрипт компиляции:
~>tex golf.tex This is TeX, Version 3.14159265 (MiKTeX 2.9 64-bit) (golf.tex a=5 % <-- 5 введено с консоли
b=8 % <-- 8 введено с консоли 13 ) No pages of output. Transcript written on golf.log.

Просьба к отвечающим писать в начале решения ваш язык, количество изменённых символов и количество символов в подготовительном коде. Также просьба давать не более одного решения на ответ.

Обновление: Конкурс окончен, вот результаты.
Побеждает ответ @Mike, сумевший уложиться в 78 подготовительных символов, и не поменять ни символа в исходном коде.
Другой ответ того же автора выглядит чрезвычайно изящно (подключение паскалевского синтаксиса как внешний модуль, хей!), и почти выиграл приз зрительских симпатий, но проигрывает по количеству символов вследствие своей большей общности. Оба решения пользуются особенностью языка Perl, который в своих модулях позволяет предобработку текста на Perl самим Perl'ом. Мощный язык, мощные средства управления синтаксисом, заслуженная победа.
Второй в списке победителей — ответ @Qwertiy. Это решение продолжает идею «получить текст как строку, обработать, чтобы получился код на нужном языке, и выполнить над ней eval», с симпатичной, очень техничной и компактной реализацией (регулярки!).
Приз зрительских симпатий получает неожиданный ответ @kmv. В этом решении текст исходной программы не объявляется строкой, а «вытягивается» из кода функции! (Это, формально говоря, решение не по стандарту, но фактически в распространённых браузерах toString() работает именно так.)
Третье место получает решение @pavel с комбинацией Unix shell/C, которое обходится без eval за счёт замены строк до компиляции и использования препроцессора C. Такой подход позволяет справиться с двоеточиями, которые вызывают затруднения для препроцессора у чистых решений на C/C++.
Вообще, идея со строкой и eval оказалась наиболее популярной, её реализуют также ответы @edem Perl, построчная замена, практически интерпретация, @Red Skotina (замена строк на Питоне, построчная адаптация текста, оставаясь в рамках правил, хотя и на грани), @gil9red (то же, но более универсально, Питон), @nuts119 на C# (да, в C# можно сделать eval, вы не знали?) и @Streletz на Java (интерпретатор из сторонней библиотеки).
Тему интерпретации продолжает ещё одно решение @nuts119 на C# с использованием DataTable как arithmetic engine. Это решение, при всей его сложности, имеет дальний прицел на построение полноценного интерпретатора.
Оставшиеся решения на чистом C/C++ и Javascript/Typescript без eval вынуждены модифицировать исходный текст, хотя они смогли обойтись минимальным количеством изменений. Из этих решений наилучшие с одним удалённым символом решения @kmv (C, препроцессор, использование битовых полей) и @Qwertiy (C++, тонкости препроцессора). Интересно, что эти оба решения убирают из исходного текста соседние символы: из := убрано в первом случае двоеточие, а во втором — знак = (!).
Оставшиеся четыре решения (@Qwertiy, typescript, @pavel, C++, препроцессор (заработало больше голосов, чем победитель), снова @Qwertiy, javascript и @Grundy, C, препроцессор) меняют больше символов в исходном коде, но также интересны и стоят вашего внимания.
Большое спасибо всем, кто принимал участие в конкурсе!

Таблица лидеров: (спасибо @Grundy за адаптацию скрипта и @jfs за идею)
function getAnswers(questionId, answer_filter, page) { return jQuery.ajax({ url: '//api.stackexchange.com/2.2/questions/' + questionId + '/answers?page=' + page + '&pagesize=100&order=desc&sort=activity&site=ru.stackoverflow&filter=' + answer_filter, method: "get", dataType: "jsonp", crossDomain: true }).then(function(data) { if (data.has_more) { return getAnswers(questionId, answer_filter, page + 1).then(function(d) { return data.items.concat(d.items); }) } return data.items; }); } function getAuthorName(e) { return e.owner.display_name } function process(items) { return items.map(function(item) { var matched = item.body.match(/\s*(.+?)\s*?,.*?(\d+),.*?(\d+)\s*?(?:[.;,(].*)?<\/h/); if (matched) { return { lang: matched[1], setup: +matched[2], changes: +matched[3], link: item.share_link, author: getAuthorName(item) }; } else { return { lang: "N/A", setup: "N/A", changes: "N/A", link: item.share_link, author: getAuthorName(item) } } }); } function sort(items) { return items.sort(function(a, b) { if (a.lang == "N/A") return 1; if (a.changes != b.changes) return a.changes - b.changes; return a.setup - b.setup; }) } function fillTemplate(sortedItems) { $('#leadership').append(sortedItems.map(function(item, index) { return $('').append($('').html(index + 1)) .append($('').html(item.author)) .append($('').html(item.lang)) .append($('').html(item.setup)) .append($('').html(item.changes)) .append($('').append($('').attr('href', item.link).text('Link'))); })); return sortedItems; } var QUESTION_ID = 526265, ANSWER_FILTER = "!4*SyY(4Kifo3Mz*lT", startPage = 1; getAnswers(QUESTION_ID, ANSWER_FILTER, startPage) .then(process) .then(sort) .then(fillTemplate); #leadership { border-collapse: collapse; } #leadership td, #leadership th { padding: 5px; } #leadership th { text-align: center; }

Таблица лидеров

Автор Язык Подготовка Изменено


Ответ

Раз в конкурсе участвует размер, вот еще вариант (лично мне нравится меньше, потому как большая ориентировка именно на этот код):
perl, подготовка 97, измененных 0
perl, подготовка 78, измененных 0
sub r{$_[0]=<>} sub wr{print@_} $_=q.
program test; var a, b, c: integer; begin readln(a); readln(b); c := a + b; writeln(c); end.
;s/\b(\w)\b/\$$1/g ;s/^\S.*|.{4}n|://gm ;eval
Тест на ideone
В коде происходит:
Создаем функции r/wr аналогичные по действию паскалевским read/write ln. $_=q. присваивает "переменной по умолчанию" весь текст до следующего знака . Все последующие строки работают с этой "переменной по умолчанию", так как явных переменных в них не указано. Перед переменными (однобуквенными словами) ставим знак $ Удаляем строки program, var, begin, end, части названий функций (readln=r, writeln=wr), двоеточия Выполняем полученный текст:
r($a); r($b); $c = $a + $b; wr($c);

Есть ли в Python оператор switch case?

Столкнулся с тем, что требуется реализовать множественное условие, которое в других языках я бы реализовал с помощью конструкции switch-case.
В Python мне приходится расписывать всё через условия if-elif-else. Это мне кажется довольно неудобным.
Есть ли более удобный способ записи подобных условий?
Например, у меня есть единицы измерения и в зависимости от выбранной мне нужно вернуть соответствующий множитель:
def get_multiplier(unit): if unit == 'mm': return 10**-3 if unit == 'cm': return 10**-2 if unit == 'dm': return 10**-1 if unit == 'm': return 1 if unit == 'km': return 10**3 raise ValueError('Undefined unit: {}'.format(unit))


Ответ

Для начала, ничего особенно плохого в использовании конструкции if-elif-else нет
При желании можно найти несколько альтернатив.

Использование словарей
Довольно распространённый способ организации конструкции switch-case в Python — это использование словаря. Проще показать на примере:
unit_to_multiplier = { 'mm': 10**-3, 'cm': 10**-2, 'dm': 10**-1, 'm': 1, 'km': 10**3 }
Для того, чтобы получить нужный множитель в этом случае требуется лишь взять значение по ключу:
try: mult = unit_to_multiplier['cm'] except KeyError as e: # можно также присвоить значение по умолчанию вместо бросания исключения raise ValueError('Undefined unit: {}'.format(e.args[0]))
Если вы твёрдо уверены, что значение всегда будет присутствовать в словаре, можете опустить блок try-except и быть готовым ловить исключение в другом месте.
Некоторой вариацией этого подходя будет предварительная проверка значения в условии:
if unit in unit_to_multiplier: mult = unit_to_multiplier[unit] else: # обработка отсутствия значения в словаре
В Python принято использовать подход, звучащий примерно так: "лучше попробовать и получить ошибку, чем каждый раз спрашивать разрешение", поэтому более предпочтительный подход с использованием исключений.
Если хочется использовать значение по умолчанию в случае, если ключ отсутствует, удобно использовать метод get
mult = unit_to_multiplier.get('ultra-meter', 0)
Если словарь вам требуется один раз, можно объединить эти выражения в одно:
unit_to_multiplier = { 'mm': 10**-3, 'cm': 10**-2, 'dm': 10**-1, 'm': 1, 'km': 10**3 }.get('km', 0)

На этом возможности этого подхода не заканчиваются. Можно использовать условные выражения в качестве ключей словаря:
def get_temp_description(temp): return { temp < -20: 'Холодно', -20 <= temp < 0: 'Прохладно', 0 <= temp < 15: 'Зябко', 15 <= temp < 25: 'Тепло', 25 <= temp: 'Жарко' }[True]
Этот словарь после вычисления будет иметь два ключа True и False. Нас интересует ключ True. Будьте внимательны, что условия не перекрываются!
Подобные словари могут проверять произвольное свойство, например, тип (источник примера):
selector = { type(x) == str : "it's a str", type(x) == tuple: "it's a tuple", type(x) == dict : "it's a dict" }[1] # можно использовать число 1 как синоним True
При необходимости более сложных действий хранить функцию в качестве значения по каждому ключу:
import operator
operations = { '+': operator.add, '*': lambda x, y: x * y, # ... }
def calc(operation, a, b): return operations[operation](a, b)
Будьте внимательны при использовании словаря с функциями — убедитесь, что вы не вызываете эти функции внутри словаря, а передаёте по ключу; иначе все функции будут выполняться каждый раз при конструировании словаря.

Другие способы
Приведены скорее для ознакомления, чем для реального использования.
Использование функций с шаблонными именами
Создадим класс, в котором напишем несколько методов вида:
def process_first(self): ...
def process_second(self): ...
...
И один метод-диспетчер:
def dispatch(self, value): method_name = 'process_' + str(value) method = getattr(self, method_name) return method()
После этого можно использовать метод dispatch для выполнения соответствующей функции, передавая её суффикс, например x.dispatch('first') Использование специальных классов
Если есть желание использовать синтаксис switch-case в максимально похожем стиле, можно написать что-то вроде следующего кода
class switch(object): def __init__(self, value): self.value = value # значение, которое будем искать self.fall = False # для пустых case блоков
def __iter__(self): # для использования в цикле for """ Возвращает один раз метод match и завершается """ yield self.match raise StopIteration
def match(self, *args): """ Указывает, нужно ли заходить в тестовый вариант """ if self.fall or not args: # пустой список аргументов означает последний блок case # fall означает, что ранее сработало условие и нужно заходить # в каждый case до первого break return True elif self.value in args: self.fall = True return True return False
Используется следующим образом:
x = int(input())
for case in switch(x): if case(1): pass if case(2): pass if case(3): print('Число от 1 до 3') break if case(4): print('Число 4') if case(): # default print('Другое число') Использование операторов and и or
Довольно небезопасный способ, см. пример:
# Условная конструкция
Select Case x Case x<0 : y = -1 Case 0<=x<1 : y = 0 Case 1<=x<2 : y = 1 Case 2<=x<3 : y = 2 Case Else : y = 'n/a' End Select
# Эквивалентная реализация на Python
y = (( x < 0 and 'first segment') or (0 <= x < 1 and 'second segment') or (1 <= x < 2 and 'third segment') or (2 <= x < 3 and 'fourth segment') or 'other segment')
Этот способ использует короткую схему вычисления операторов and и or, т.е. то, что логические выражения вычисляются следующим образом:
(t вычисляющееся как True, f вычисляется как False):
f and x = f t and x = x f or x = x t or x = t
Предложенный способ вычислений будет работать только в том случае, если второй аргумент оператора and всегда будет содержать True-выражение, иначе этот блок всегда будет пропускаться. Например:
y = (( x < 0 and -1) or (0 <= x < 1 and 0) or (1 <= x < 2 and 1) or (2 <= x < 3 and 2) or 'n/a')
Будет работать неправильно в случае 0 <= x < 1, т.к. выражение 0 <= x < 1 and 0 равно 0, и из-за этого управление перейдёт следующему аргументу or, вместо того, чтобы вернуть этот ноль в качестве результата выражения. Использование исключений
Если объявить несколько функций:
import sys
class case_selector(Exception): def __init__(self, value): # один обязательный аргумент Exception.__init__(self, value)
def switch(variable): raise case_selector(variable)
def case(value): exc_сlass, exс_obj, _ = sys.exc_info() if exc_сlass is case_selector and exс_obj.args[0] == value: return exс_class return None
Здесь используется функция sys.exc_info, которая возвращает набор из информации об обрабатываемом исключении: класса, экземпляра и стека.
Код с использованием этих конструкций будет выглядеть следующим образом:
n = int(input()) try: switch(n) except ( case(1), case(2), case(3) ): print "Число от 1 до 3" except case(4): print "Число 4" except: print "Другое число"

Книги и учебные ресурсы по фундаментальным знаниям и навыкам разработчика

Рекомендуемая литература и документация по знаниям и навыкам разработки, не привязанным к конкретным языкам и платформам.
Все эти книги следовало бы добавить в каждый список литературы, но мы следуем принципу DRY и сохраняем их здесь, в одном месте.
Дополнения к ответу всячески приветствуются!

Данный перечень входит в поддерживаемый сообществом Сборник учебных ресурсов по программированию


Ответ

Архитектура ПО
Structure and Interpretation of Computer Programs - 2nd Edition. Harold Abelson, Gerald Jay Sussman, Julie Sussman
Русский перевод: Структура и Интерпретация Компьютерных Программ. Харольд Абельсон, Джеральд Джей Сассман
Алгоритмы и структуры данных
The Algorithm Design Manual. Steven S Skiena. 2008
Русский перевод: Алгоритмы. Руководство по разработке. Стивен Скиена. 2014 г. Introduction to Algorithms, 3rd Edition. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. 2009
Русский перевод: Алгоритмы. Построение и анализ. Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн. 2015 г. The Art of Computer Programming. Donald E. Knuth. 2011
Русский перевод: Искусство программирования. Дональд Э. Кнут. 2015 г. Algorithms + Data Structures = Programs. Niklaus Wirth, 1976 (or Algorithms + Data Structures. 2004.
Русский перевод: Алгоритмы + структуры данных = программы. М.: Мир, 1985, Алгоритмы и структуры данных. М.: Мир, 1989, Алгоритмы и структуры данных. Новая версия для Оберона. М.: ДМК Пресс, 2010. Старая версия книги, в отличие от новых, содержит подробно разобранный компилятор простого языка.
Проектирование и стиль кода
Design Patterns: Elements of Reusable Object-Oriented Software. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Русский перевод: Приёмы объектно-ориентированного проектирования. Паттерны проектирования. Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидс Patterns of Enterprise Application Architecture. Martin Fowler
Русский перевод: Архитектура корпоративных программных приложений. Мартин Фаулер Domain-Driven Design: Tackling Complexity in the Heart of Software. Eric Evans
Русский перевод: Предметно-ориентированное проектирование. Структуризация сложных программных систем. Эрик Эванс «Совершенный код» (Code Complete). Стив Макконнелл «Рефакторинг. Улучшение существующего кода». Мартин Фаулер «Чистый код. Создание, анализ и рефакторинг» (Clean Code: A Handbook of Agile Software Craftsmanship). Роберт Мартин
Навыки разработчика
Эндрю Хант, Дэвид Томас — «Программист-прагматик. Путь от подмастерья к мастеру»
Организация процесса разработки
Том ДеМарко — «Deadline. Роман об управлении проектами» Роберт Мартин — «Быстрая разработка программного обеспечения» Джоэл Спольски — «И снова о программировании». Джо Мараско — «IT-проекты. Фронтовые очерки» Стив Макконнелл — «Сколько стоит программный проект» Джин Ким, Кевин Бер, Джордж Спаффорд — «Проект «Феникс». Роман о том, как DevOps меняет бизнес к лучшему».
Синтаксический разбор и компиляция
Compilers: Principles, Techniques, and Tools, Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman
Русский перевод: Компиляторы: принципы, технологии и инструментарий, Альфред Ахо, Моника С. Лам, Рави Сети, Джеффри Ульман. Известна как «Книга Дракона».

Наглядный пример различия DTO, POCO (POJO) и Value Object

Навеяно статьёй о различиях DTO, POCO и Value Object на Хабрахабре: DTO vs POCO vs Value Object, а также вопросом POCO vs DTO
Нигде нет конкретных примеров. Приведите, пожалуйста, конкретный пример с небольшим описанием (или также примером), где и как его использовать и для чего.
UPD
Отличные ответы. Всем спасибо.
Еще небольшой вопрос по использованию POCO. Когда и насколько рационально запихивать логику в объекты? Вот к примеру, у меня есть сервисный слой, который возвращает POCO, какие именно методы я туда могу вставить? Допустим, мне нужно валидировать Кастомера, ок, я сделал в POCO метод Validate, пока мне не нужно лезть для валидации в базу - все хорошо, но как только это понадобиться, идея уже не кажется такой хорошей. Или я не прав? Сейчас у меня приложение, где почти все действия выполняет бизнес слой, в моделях только простые методы типа GetFullName, и по сути я оперирую DTO-хами. Так вот, как уследить ту тонкую грань "что в POCO, что в сервисе" или вообще "всю логику в сервисы, оперировать DTO"?


Ответ

Представим некоторый интернет магазин. У этого магазина есть веб-интерфейс и сервер приложений, который обрабатывает логику. Некий пользователь хочет совершить какой-нибудь заказ. Для этого ему нужно выполнить ряд действий: добавить нужные товары в корзину и подтвердить заказ.
Для того, чтобы это сделать, на сервере приложений может существовать класс Order
public class Order { private ItemDiscountService _itemDiscountService; private UserService _userService;
public Order(ItemDiscountService itemDiscountService, UserService userService) { _itemDiscountService = itemDiscountService; _userService = userService }
public int Id { get; set; } public List Items { get; set; } public decimal Subtotal { get;set; } public decimal Discount { get; set; }
public void AddItem(Item item) { Items.Add(item); CalculateSubtotalAndDiscount(); }
public void CalculateSubtotalAndDiscount() { decimal subtotal = 0; decimal discount = 0; foreach (var item in Items) { var currentCost = item.Cost * _itemDiscountService.GetDiscountFactor(item) * _userService.GetCurrentUserDiscountFactor(); subtotal += currentCost; discount += item.Cost - currentCost; }
Subtotal = subtotal; Discount = discount; } }
Этот класс содержит в себе данные и логику их изменения. Он не унаследован от какого-либо специфического класса из сторонней библиотеки или от какого-либо стороннего класса и является достаточно простым - Plain Old CLR/Java Object

Когда пользователь добавляет что-то в корзину, эта информация передаётся на сервер приложений, что вызывает метод AddItem в классе Order, который пересчитывает стоимость товаров и скидку, меняя тем самым состояние заказа. Нужно отобразить пользователю это изменение, и для этого нужно передать обновлённое состояние обратно на клиент.
Но мы не можем просто передать экземпляр нашего Order или его копию, так как он зависит от других классов (ItemDiscountService, UserService), которые в свою очередь могут зависеть от других классов, которым может быть нужно соединение с базой данных и т. п.
Конечно, их можно продублировать на клиенте, но тогда на клиенте будет доступна вся наша логика, строка подключения к БД и т. п., чего мы показывать совершенно не хотим. Поэтому, чтобы просто передать обновленное состояние, мы можем сделать для этого специальный класс:
public class OrderDto { public int Id { get; set; } public decimal Subtotal { get; set; } public decimal Discount { get; set; } public decimal Total { get; set; } }
Мы сможем поместить в него те данные, которые хотим передать на клиент, создав тем самым Data Transfer Object. В нем могут содержаться совершенно любые нужные нам атрибуты. В том числе и те, которых нет в классе Order, например, атрибут Total

У каждого заказа есть свой идентификатор - Id, который мы используем для того, чтобы отличать один заказ от другого. В то время как в памяти сервера приложений может существовать заказ с Id=1, содержащий в себе 3 предмета, в БД может хранится такой же заказ, с тем же идентификатором, но содержащий в себе 5 предметов. Такое может возникнуть, если мы прочитали состояние заказа из БД и поменяли его в памяти, не сохранив изменения в БД.
Получается, что несмотря на то, что некоторые значения у заказа в БД и заказа в памяти сервера приложений будут отличаться, это все равно будет один и тот же объект, так как их идентификаторы совпадают.
В свою очередь значение стоимости - 100, номер идентификатора - 1, текущая дата, имя текущего пользователя - "Петрович" будут равны аналогичным значениям только тогда, когда эти значения будут полностью совпадать, и никак иначе.
Т. е. 100 может быть равно только 100, "Петрович" может быть равен только "Петрович" и т. д. И неважно, где будут созданы эти объекты. Если их значения будут полностью совпадать - они будут равны. Такие объекты называются Value Object
Помимо уже существующих Value Object типа decimal или string можно создавать и свои. В нашем примере мы могли бы создать тип OrderPrice и поместить туда поля Subtotal, Total и Discount.
public struct OrderPrice { public decimal Subtotal; public decimal Discount; public decimal Total; }
В c# есть подходящая для этого возможность создавать значимые типы которые сравниваются по значению и при присваивании целиком копируются.

UPDATE Что касается обновленного вопроса (хоть это действительно отдельный большой вопрос, как заметил Discord):
Когда мы разрабатываем приложение мы работаем с какой-либо предметной областью. Эта предметная область может быть выражена в виде некоторой модели и действий, которые меняют состояние этой модели. Все это может быть представлено в виде набора классов. Такие классы содержат в себе как данные (в виде полей классов) так и действия, которые этими данными манипулируют (в виде методов).
В принципе нет никаких ограничений на размещение данных или методов по классам. Можно вообще все засунуть в один класс и это будет прекрасно работать. Основная проблема заключается в том, что такой код будет сложнее, а значит дороже поддерживать. Так как все будет переплетено между собой - любые изменения могут привносить кучу ошибок и т.п. Поэтому, для достижения более "дешевого" кода мы начинаем его как-то структурировать, разбивать на модули и т.п.
Мы можем разложить данные в одни классы, а методы в другие и это тоже будет работать и будет даже более модульно. Но все равно может нести ряд минусов. Глядя на кучу данных может быть не очевидным то, что вообще с ними может происходить или кому они могут быть нужны. Тоже самое и с кучей методов. Поэтому, чтобы было еще удобнее можно разложить данные по классам как-то сгруппировав их понятным образом. Тоже самое и с методами. Данные заказа, пользователя, товара и т.п. могут стать отдельными классами так же как и классы с соответствующими методами. Это будет еще модульнее и понятнее. Но у любого подхода есть свои плюсы и минусы.
Например, в нашем интернет магазине есть различные товары, логика расчета цены которых может быть достаточно сложной. Представим, что есть некий базовый класс Item, и множество производных классов:
public class Item { public int Id {get;set;} public string Name {get;set;} public decimal BaseCost {get;set;} public decimal Cost {get;set;} }
public class Boots : Item { ... } public class Shirt : Item { ... } public class Pants : Item { ... }
Так как логика у нас находится в отдельных классах, представим что есть класс ItemCostService, который умеет рассчитывать стоимость товара. Тогда, из-за наличия большого числа различных условий он может выглядеть как-то так:
public class ItemCostService { public decimal CalculateCost(Item item) { if(item is Boots) { item.Cost = ... } else if (item is Shirt) { item.Cost = ... } else if .... } }
И таких мест в программе, где в зависимости от конкретного типа товара должно быть различное поведение может быть много. Конечно, это все будет работать. Но, как только у нас появляется новый тип товара, или поменяется логика обработки существующего типа товара нам придется изменить код в большом количестве мест везде, где присутствуют такие условия. Это сложнее, чем поменять все в одном месте, дольше и чревато тем, что можно что-то забыть сделать.
В данном вопросе мы говорим о языках, основной парадигмой которых является ООП. А это значит, что существует готовая инфраструктура которая поддерживает основные принципы ООП. Чтобы следовать этой парадигме и получать выгоду от готовой инфраструктуры мы можем поменять наши класс, добавив логику вычисления стоимости в них, меняя ее по необходимости в производных классах:
public class Item { ... public virtual void CalculateCost() { ... } }
public class Boots : Item { public override void CalculateCost() { ... } }
Каждый производный тип сам сможет определить логику своего поведения. Вся она будет в одном месте, рядом с данными. А какой из конкретных методов вызвать определит уже инфраструктура избавив нас от этой головной боли. В данном примере такой подход будет более удобен, т.к. у нас пропадет необходимость создавать куче if'ов по всему коду, что только упростит программу и сделает изменения более простыми.
Ну и опять же - все зависит от ситуации. Серебряной пули не бывает и в различных случаях стоит использовать различные подходы, которые будут более дешевы в каждой конкретной ситуации. Еще немного про ООП и остальное можете посмотреть в моей статье тут

Стоит ли заморачиваться с шаблонами проектирования?

На данный момент изучаю шаблоны проектирования и пробую применять их на практике, но из-за небольшого опыта работы с ними и отсутствия менторства в этом деле прошу у вас помощи.
Есть, например, задача - сделать стрипт для связи с людьми.
Подробней: Мы говорим что хотим связаться с пользователем определенным способом, задаем вспомогательную информацию для объекта, далее говорим отправить. В идеале вижу использование скрипта в виде с использованием фабрики и стратегии:
$object = Communication::GetDriver('sms'); $object->setMsg($text); $object->setTelephone($phone); $object->send();
Но может потребоваться отправить совершенно другим способом, например, в соцсеть.
$object = Communication::GetDriver('socialNetwork'); $object->setMsg($text); $object->setIdUser($id); $object->send();
Вот и вопрос, как лучше поступить? Стоит ли заморачиваться с шаблонами? Может быть, следует сделать все это отдельными классами? Это будет оправдано? При этом желательно ловить ошибки и вести лог происходящего.
А если использовать шаблоны то как объединить классы? Может быть, есть методы, как сделать лучше?


Ответ

Стоит. Только нужно внести ясность.
Обычно полагают, что код пишется "на один раз" или "код слишком простой". На практике часто оказывается, что "да, оно одноразовое, но надо добавить это...и это". В итоге рождается привычка сразу делать структурно - выделить классы, за которыми спрятать лапшу (точек расширения не надо, просто порядок), а в случае веба взять любой фреймворк, а не тешить себя мыслями "да тут делов то ерунда, роутинг простой, шаблоны не надо"
Практически всегда есть смысл разделить на логгеры, манагеры, ридеры, чтобы оперировать более высокими уровнями абстракции и спрятать за ними детали, если вы не пишете админские скрипты или не фрилансите (написал, отдал, забыл)
Главное не забывать, что шаблоны призваны решать общие проблемы общими способами. Поэтому не должно быть "ооп ради ооп" и "шаблоны ради шаблонов". Тут многие совершают ошибку - начитаются умных книг и пытаются применить шаблон.
Суть в том, что шаблоны - общие решения, которые как то названы. При решении проблем люди приходят к общим решениям, называют его и пишут в книжку. И шаблоны полезны - знание шаблонов может подсказать приемлемые варианты и мозг приучается видеть их сразу. Впрочем, даже если не знать ни одного, то на практике все равно сам их изобретаешь. Также помогают в коммуникации (назвать шаблон проще, чем пояснять, что за структура классов и как она связана). Но решать то нужно задачи, потому на задачах и нужно акцентироваться, а не на шаблонах.
Правильный путь такой: берем правило "делай все проще", добавляем в него "главная сила ооп не в 3 китах, а в гибкости объектов и возможности комбинировать их", приправляем приемами чистого кода и других парадигмами - и решаем задачу. Если видим, что подходит какой то шаблон (обычно не понять какой точно, слишком тонкая грань), то идем к нему решая задачу. Как только задача решена, то все - дальше реализовывать шаблон не нужно. Нет причины - нет кода. Неважно насколько решение получилось "классическим" - любое продолжение есть усложнение.
Так что разбираться в шаблонах полезно - это как перенимать чужой опыт решения типичных проблем. И использовать полезно. Но чтобы избежать попадания в ловушку "шаблоны ради шаблонов" следует использовать прием, каким шаблон решает задачу, закодить его и остановиться.