Erlang Central

Difference between revisions of "Decoding Binary Messages"

From ErlangCentral Wiki

(the problem isn't as hard as the code makes it out to be: replaced with a more compact solution; fixed erroneous invocation of io_lib:format/2; minor clean-ups)
Line 7:Line 7:
 
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.  
 
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
 
 
<code>
 
<code>
% to_byte_strings : string -> [string]
+
decode_message(L) -> decode_message(L,[]).
%  chop the string s into pieces of length 8
+
decode_message([],Acc) -> lists:reverse(Acc);
%  E.g. to_byte_strings("0000000011111111").
+
decode_message(L,Acc) ->
%  = ["00000000", "11111111"]
+
     {BinC,T} = lists:split(8,L),
to_byte_strings(Accum, []) -> lists:reverse(Accum);
+
     {ok,[C],[]} = io_lib:fread("~2u",BinC),
to_byte_strings(Accum, Remain) ->
+
     decode_message(T,[C|Acc]).
     {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
 
%% The following message appeared on a ThinkGeek tshirt
 
%% on the first of april 2004.
 
%% on the first of april 2004.
1> Thinkgeek_tshirt_message =
+
1> decode_message(
 
1>    "010010010010000001110011011010000110111"
 
1>    "010010010010000001110011011010000110111"
 
1>    "101110000011100000110010101100100001000"  
 
1>    "101110000011100000110010101100100001000"  
Line 55:Line 33:
 
1>    "010010111001100100000011011000110111101"  
 
1>    "010010111001100100000011011000110111101"  
 
1>    "110101011100110111100100100000011100110"  
 
1>    "110101011100110111100100100000011100110"  
1>    "110100001101001011100100111010000100001".
+
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!
 
"I shopped at ThinkGeek on April Fools Day, and all I got was this lousy shirt!
 
</code>
 
</code>
Line 73:Line 39:
 
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:  
 
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:  
 
<code>
 
<code>
1> io_lib:fread("~d", "100").
+
2> io_lib:fread("~d", "100").
 
{ok,"d",[]}
 
{ok,"d",[]}
 
% Note:  The "d" is just an aspect of Erlang's odd handling of
 
% Note:  The "d" is just an aspect of Erlang's odd handling of
 
% strings as lists of decimal numbers.  The ASCII value of the
 
% strings as lists of decimal numbers.  The ASCII value of the
 
% character "d" is 100:
 
% character "d" is 100:
2> $d.
+
3> $d.
 
100
 
100
3> io_lib:format("~B", 100).
+
4> io_lib:format("~B", [100]).
 
["100"]
 
["100"]
 
</code>
 
</code>
  
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:
+
Both procedures take an additional 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 use "~16u" instead.
 
+
<code>
+
% 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
+
  
 
</code>
 
</code>
  
 
[[Category:CookBook]][[Category:StringRecipes]]
 
[[Category:CookBook]][[Category:StringRecipes]]

Revision as of 10:09, 25 September 2006

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.

decode_message(L) -> decode_message(L,[]).
decode_message([],Acc) -> lists:reverse(Acc);
decode_message(L,Acc) ->
    {BinC,T} = lists:split(8,L),
    {ok,[C],[]} = io_lib:fread("~2u",BinC),
    decode_message(T,[C|Acc]).

%% The following message appeared on a ThinkGeek tshirt
%% on the first of april 2004.
1> decode_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").
"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:

2> io_lib:fread("~d", "100").
{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:
3> $d.
100
4> io_lib:format("~B", [100]).
["100"]

Both procedures take an additional 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 use "~16u" instead.

</code>