Erlang Central

# Decoding Binary Messages

## Problem

You have a string containing the characters 0 and 1, representing, in binary, the ASCII values of some text. You want to convert this string into normal, readable, ASCII.

## Solution

This would probably be an appropriate place to demonstrate some of Erlang's built-in support for communicating with remote systems, converting text into binary format, and so forth. But, we will save those topics for their own recipes. This topic will be treated as was done in the original Schematics Cookbook.

The solution has three steps:

Chop the string into its component parts. ASCII values are 8 bits long, so we chop the string into sub-strings that are 8 characters long Convert each substring into the number it represents. Luckily for us Erlangers, this is equivalent to the character value, and can be treated as such. Combine all the ASCII characters into a single string

% to_byte_strings : string -> [string]
%  chop the string s into pieces of length 8
%  E.g. to_byte_strings("0000000011111111").
%   = ["00000000", "11111111"]
to_byte_strings(Accum, []) -> lists:reverse(Accum);
to_byte_strings(Accum, Remain) ->
{List_of_8, Rest} = lists:split(8, Remain),
to_byte_strings([List_of_8|Accum], Rest).
to_byte_strings(Str) ->
to_byte_strings([], Str).

% bin_str_to_integer : string-of-zeros-and-ones -> integer
%  converts a string of binary digits into an integer
%  E.g. bin_str_to_integer("101") = 5
bin_str_to_integer(B) ->
{ok, [Num|_], _} = io_lib:fread("~2u", B),
Num.

% decode : string -> string
%  converts a message written in binary to a normal string
decode_message(Str) ->
Decoded = lists:map(fun(X) -> bin_str_to_integer(X) end,
to_byte_strings(Str)).

%% The following message appeared on a ThinkGeek tshirt
%% on the first of april 2004.
1> Thinkgeek_tshirt_message =
1>     "010010010010000001110011011010000110111"
1>     "101110000011100000110010101100100001000"
1>     "000110000101110100001000000101010001101"
1>     "000011010010110111001101011010001110110"
1>     "010101100101011010110010000001101111011"
1>     "011100010000001000001011100000111001001"
1>     "101001011011000010000001000110011011110"
1>     "110111101101100011100110010000001000100"
1>     "011000010111100100101100001000000110000"
1>     "101101110011001000010000001100001011011"
1>     "000110110000100000010010010010000001100"
1>     "111011011110111010000100000011101110110"
1>     "000101110011001000000111010001101000011"
1>     "010010111001100100000011011000110111101"
1>     "110101011100110111100100100000011100110"
1>     "110100001101001011100100111010000100001".
"0100100100100000011100110110100001101111011100000111000001100101011
00100001000000110000101110100001000000101010001101000011010010110111
00110101101000111011001010110010101101011001000000110111101101110001
00000010000010111000001110010011010010110110000100000010001100110111
10110111101101100011100110010000001000100011000010111100100101100001
00000011000010110111001100100001000000110000101101100011011000010000
00100100100100000011001110110111101110100001000000111011101100001011
10011001000000111010001101000011010010111001100100000011011000110111
10111010101110011011110010010000001110011011010000110100101110010011
1010000100001"
decode_message(Thinkgeek_tshirt_message).
2> cookbook:decode_message(Thinkgeek_Tshirt_Message).
"I shopped at ThinkGeek on April Fools Day, and all I got was this lousy shirt!

The Erlang system comes with io_lib functions to convert from strings to numbers (via io_lib:fread) and vice-versa (via io_lib:format). For example:

{ok,"d",[]}
% Note:  The "d" is just an aspect of Erlang's odd handling of
% strings as lists of decimal numbers.  The ASCII value of the
% character "d" is 100:
2> \$d.
100
3> io_lib:format("~B", 100).
["100"]

Both procedures take an additional (optional) argument, which specifies the radix ("base") to use. The default is 10, but values of 2 through 36 may also be used. In the recipe above the radix 2 (format of "~2u") specifies that we are converting to numbers from a base 2 representation. If, for example, the numbers were represented in base 16 (hexadecimal) it would be a simple matter to replace bin_string_to_integer with the procedure:

% hex_str_to_integer : string-of-0-to-9-and-A-to-F -> integer
%  converts a string of binary digits into an integer
%  E.g. hex_str_to_integer "A9FF") = 43519
hex_str_to_integer(Hval) ->
{ok, [Num|_], _} = io_lib:fread("~16u", Hval),
Num.
4> hex_str_to_integer("A9FF").
43519