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字节(因为后缀++
返回了先前的值)。
让我们看看其余的:
*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 ,我们可以对字符进行按位与运算以使其为零,否则为非零。
Digging deeper, in the first "if" branch, we now have a character that's one of FBizu
. I chose only to update the accumulator on F
s and B
s, so I needed some way to filter out the izu
s. Conveniently, F
and B
both have only the second, third, or seventh least significant bits set, and all the other numbers have at least one other bit set. In fact, they all have either the first or fourth least significant bit. Hence, we can bitwise AND with 0b00001001
, which is 9, which will yield 0 for F
and B
and nonzero otherwise.
一旦确定了F
or B
,就可以通过将它们的模数5 映射到0
和1
,因为F
is 70
和B
is 66
。然后是片段
i += i + *s % 5;
只是一种打招呼的方式
i = (i * 2) + (*s % 5);
也可以表示为
i = (i << 1) | (*s % 5);
这会将新位插入最低有效位置,并将其他所有位置移到1以上。
“可是等等!” 你可能会抗议。“打印后i
,什么时候可以将其重置为0?” 好吧,putchar
将其参数强制转换为unsigned char
,恰好大小为8位。这意味着超过第8个最低有效位(即以前的迭代中的垃圾)的所有内容都将被丢弃,并且我们不必为此担心。
Thanks to @ETHproductions for suggesting to replace 57
with 9
, saving a byte!