C, 59 bytes
i;f(char*s){while(*s&3?*s&9||(i+=i+*s%5):putchar(i),*s++);}
Magic numbers, magic numbers everywhere!
(Also, C shorter than Python, JS, PHP, and Ruby? Unheard of!)
This is a function that takes a string as input and outputs to STDOUT.
Walkthrough
The basic structure is:
i; // initialize an integer i to 0
f(char*s){
while(...); // run the stuff inside until it becomes 0
}
Here, the "stuff inside" is a bunch of code followed by ,*s++
, where the comma operater returns only the value of its second argument. Hence, this will run through the string and set *s
to every character, including the trailing NUL byte (since postfix ++
returns the previous value), before exiting.
Let's take a look at the rest:
*s&3?*s&9||(i+=i+*s%5):putchar(i)
Peeling away the ternary and short circuiting ||
, this can be expanded to
if (*s & 3) {
if (!(*s & 9)) {
i += i + *s % 5;
}
} else {
putchar(i);
}
Where do these magic numbers come from? Here are the binary representations of all the characters involved:
F 70 01000110
B 66 01000010
i 105 01101001
z 122 01111010
u 117 01110101
32 00100000
\0 0 00000000
First, we need to separate space and NUL from the rest of the characters. The way this algorithm works, it keeps an accumulator of the "current" number, and prints it whenever it reaches a space or the end of the string (i.e. '\0'
). By noticing that ' '
and '\0'
are the only characters to not have any of the two least significant bits set, we can bitwise AND the character with 0b11
to get zero if the character is space or NUL and nonzero otherwise.
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.
Once we've determined that we have an F
or B
, we can map them to 0
and 1
respectively by taking their modulus 5, because F
is 70
and B
is 66
. Then the snippet
i += i + *s % 5;
is just a golfy way of saying
i = (i * 2) + (*s % 5);
which can also be expressed as
i = (i << 1) | (*s % 5);
which inserts the new bit at the least significant position and shifts everything else over 1.
"But wait!" you might protest. "After you print i
, when does it ever get reset back to 0?" Well, putchar
casts its argument to an unsigned char
, which just so happens to be 8 bits in size. That means everything past the 8th least significant bit (i.e. the junk from previous iterations) is thrown away, and we don't need to worry about it.
Thanks to @ETHproductions for suggesting to replace 57
with 9
, saving a byte!