Elixircism 02
Elixircism 02 - Secret Handshake
Welcome back. In this episode we need to crack the secret handshake given to us by this definition
You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake".
1 = wink 10 = double blink 100 = close your eyes 1000 = jump
10000 = Reverse the order of the operations in the secret handshake.
Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.
Here's a couple of examples:
Given the input 3, the function would return the array ["wink", "double blink"] because 3 is 11 in binary.
We're given a hint to use
Bitwise
so let's not look a gift horse in the mouth, and make use of that. Basically it allows us to do bitwise operations like you would find in other languages like C. For example:
iex(1)> use Bitwise
Bitwise
iex(2)> 5 &&& 1
1
iex(3)> 5 &&& 2
0
Here we are performing an and
operation on 5
which is 101
in binary, it's that simple.
Now we know our brief, and we need to get started. Our input is a base 10 number, which
can have up to 5 bits set: 11111 == 31
and each set bit is a part of the secret handshake.
Out first thought is that we need to iterate over each set bit in the number provided. Luckily for us, Elixir has a handy function for just that task:
digits(integer, base \ 10)
Returns the ordered digits for the given integer.
An optional base value may be provided representing the radix for the returned digits. This one must be an integer >= 2.
This is ideal as it returns the digits for whatever base we desire, which in our case is 2. So that's the first step done, but as we need to process our bits from lowest to highest, we also need to reverse it. So our first steps are
Integer.digits(code, 2)
|> Enum.reverse
Next we need to figure out how we map the set bit to our actions. Well the clue is
in the action: map
- let's create a map that will allow us to lookup an action string.
@message %{0 => "wink", 1 => "double blink", 2 => "close your eyes", 3 => "jump"}
This maps the index of the set bit to an action. If the bit at position 0
is set,
we get a wink
for example. In order for us to be able to do that we need to know the index
of the bits as we iterate over them. This is easily done via
> with_index(enumerable, offset \\ 0)
> Returns the enumerable with each element wrapped in a tuple alongside its index.
Excellent. So let's see where we are now:
Integer.digits(code, 2)
|> Enum.reverse()
|> Enum.with_index()
iex(3)> Integer.digits(5, 2) |> Enum.reverse |> Enum.with_index
[{1, 0}, {0, 1}, {1, 2}]
Our result is now a list of tuples with the first element of the tuple being the bit,
and the second the index. All we need to do now is lookup the action in our map if the bit is 1
. Once again we can make use of the Enum.map
function to accomplish this
Integer.digits(code, 2)
|> Enum.reverse()
|> Enum.with_index()
|> Enum.map(fn {v, i} -> if v == 1 do @message[i] end end )
What happens here is that for each tuple in the list, we match the value (v
) and
index (i
) and if the value is 1
, we lookup the action in our @messages
map and return it. It's a lot more clear when we run it
iex(4)> message = %{0 => "wink", 1 => "double blink", 2 => "close your eyes", 3 => "jump"}
%{0 => "wink", 1 => "double blink", 2 => "close your eyes", 3 => "jump"}
iex(5)> Integer.digits(5, 2) |> Enum.reverse |> Enum.with_index |> Enum.map(fn {v, i} -> if v == 1 do message[i] end end )
["wink", nil, "close your eyes"]
Almost perfect, except for that rogue nil
which isn't any use to us. That appears when the bit isn't set and there's nothing returned inside the map function. So what we need to do is filter out any nil
values. And as luck would have it, Enum
provides us with a filter
function which works as you'd expect:
filter(enumerable, fun)
Filters the enumerable, i.e. returns only those elements for which
fun
returns a truthy value.
Thus making our code look like this
result = Integer.digits(code, 2)
|> Enum.reverse
|> Enum.with_index
|> Enum.map(fn {v, i} -> if v == 1 do @message[i] end end )
|> Enum.filter(fn x -> x != nil end)
iex(6)> Integer.digits(5, 2) |> Enum.reverse |> Enum.with_index |> Enum.map(fn {v, i} -> if v == 1 do message[i] end end ) |> Enum.filter(fn x -> x != nil end)
["wink", "close your eyes"]
Perfect! We're almost done now. the only thing that's left is to handle the reverse
action, if the fifth bit is set. Not a problem for us, let's make use of that Bitwise
we were told about earlier
if (code &&& 16) != 0 do
Enum.reverse(result)
else
result
end
So after our sequence of operations above, the action is list is stored in result
.
Then we check the fifth bit of the original code to see if it's set. If it is we simply
reverse the list with Enum.reverse\1
, otherwise we return the result as is.
Now if we run the tests, we should see them all pass, and we've completed the handshake exercise.
If you have a better approach or any questions, leave a comment below.
See you next time.