C, 59 байт
i;f(char*s){while(*s&3?*s&9||(i+=i+*s%5):putchar(i),*s++);}
Магические числа, магические числа везде!
(Кроме того, C короче, чем Python, JS, PHP и Ruby? Не слышно!)
Это функция, которая принимает строку в качестве входных данных и выводит их в STDOUT.
Прохождение
Базовая структура:
i; // initialize an integer i to 0
f(char*s){
while(...); // run the stuff inside until it becomes 0
}
Здесь «вещи внутри» - это набор кода, за которым следует оператор ,*s++
запятой, который возвращает только значение своего второго аргумента. Следовательно, он будет проходить через строку и устанавливаться *s
для каждого символа, включая завершающий байт NUL (поскольку postfix ++
возвращает предыдущее значение), перед выходом.
Давайте посмотрим на остальное:
*s&3?*s&9||(i+=i+*s%5):putchar(i)
Сняв тройное и короткое замыкание ||
, это можно расширить до
if (*s & 3) {
if (!(*s & 9)) {
i += i + *s % 5;
}
} else {
putchar(i);
}
Откуда берутся эти магические числа? Вот двоичные представления всех задействованных символов:
F 70 01000110
B 66 01000010
i 105 01101001
z 122 01111010
u 117 01110101
32 00100000
\0 0 00000000
Во-первых, нам нужно отделить пробел и NUL от остальных символов. Как работает этот алгоритм, он сохраняет накопитель «текущего» числа и печатает его всякий раз, когда он достигает пробела или конца строки (то есть '\0'
). Заметив, что ' '
и '\0'
являются единственными символами, которые не имеют ни одного из двух младших значащих битов, мы можем побитово И символ 0b11
получить, чтобы получить ноль, если символ является пробелом или NUL и ненулевым в противном случае.
Копая глубже, в первой ветке "если" у нас теперь есть персонаж, один из которых FBizu
. Я выбрал только обновить аккумулятор на F
s и B
s, поэтому мне нужен был способ отфильтровать izu
s. Удобно, F
и для B
обоих установлены только второй, третий или седьмой младший значащий бит, а для всех остальных чисел установлен по меньшей мере один другой бит. На самом деле все они имеют первый или четвертый младший бит. Следовательно, мы можем поразрядно AND с 0b00001001
, который равен 9, что даст 0 для F
и B
и ненулевой в противном случае.
Как только мы определили, что у нас есть F
или B
, мы можем сопоставить их 0
и, 1
соответственно, взяв их модуль 5, потому что F
есть 70
и B
есть 66
. Тогда фрагмент
i += i + *s % 5;
это просто способ сказать
i = (i * 2) + (*s % 5);
который также может быть выражен как
i = (i << 1) | (*s % 5);
который вставляет новый бит в наименее значимую позицию и сдвигает все остальное на 1.
"Но ждать!" Вы могли бы возразить. «Когда вы печатаете i
, когда он возвращается к 0?» Ну, putchar
приведем свой аргумент к an unsigned char
, который просто так получается размером 8 бит. Это означает, что все, что осталось за восьмым младшим значащим битом (т.е. мусор из предыдущих итераций), отброшено, и нам не нужно об этом беспокоиться.
Спасибо @ETHproductions за предложение заменить 57
на 9
, сохраняя байт!