Tanse
Tanse

Reputation: 97

String function clause matching

I'm running into a problem when writing some simple erlang code for an old Advent of Code task.

The following program is supposed to read lines, group characters in a string by occurrence and then count the number of lines that have a repeat of three characters.


count_occurrences([], Map) -> Map;
count_occurrences([H | T], Map) ->
    count_occurrences(T, maps:put(H, maps:get(H, Map, 0) + 1, Map)).

count(Line, Count) ->
    Map = count_occurrences(Line, #{}),
    case lists:member(3, maps:values(Map)) of
        true -> Count + 1;
        false -> Count
    end.

run() ->
    {ok, Binary} = file:read_file("data.txt"),
    Lines = binary:split(Binary, <<"\n">>, [global]),
    Result = lists:foldl(fun count/2, 0, Lines),
    Result.

However, I get this error message:

10> c(day2).   
{ok,day2}
11> day2:run().
** exception error: no function clause matching day2:count_occurrences(<<"bpacnmelhhzpygfsjoxtvkwuor">>,#{}) (day2.erl, line 5)
     in function  day2:count/2 (day2.erl, line 10)
     in call from lists:foldl/3 (lists.erl, line 1263)

I don't understand why <<"bpacnmelhhzpygfsjoxtvkwuor">>,#{} doesn't match the second "count_occurrences" function clause - a string is the same as a list, right? Why doesn't it match [H | T]?

Upvotes: 3

Views: 207

Answers (3)

Ulf Wiger
Ulf Wiger

Reputation: 121

Note that you can also iterate through a binary with only slightly different syntax:

count_occurrences(<<>>, Map) -> Map;
count_occurrences(<<H, T/binary>>, Map) ->
    count_occurrences(T, maps:put(H, maps:get(H, Map, 0) + 1, Map)).

By default, H is assumed to be a byte, but you can add modifiers to specify how many bits you want to select, and more. See the documentation for the Bit Syntax.

Upvotes: 2

7stud
7stud

Reputation: 48649

Check out this example:

-module(a).
-compile(export_all).

go([_H|_T], _X) ->
    "First arg was a list";
go("a", _X) ->
    "First arg was a string";
go(<<"a">>, _X) -> 
    "First arg was a binary".

In the shell:

5> a:go(<<"a">>, #{a=>1, b=>2}).
"First arg was a binary"

and:

6> a:go("a", #{a=>1, b=>2}).    
"First arg was a list"

a string is the same as a list, right?

Yes, a double quoted string is a shortcut for creating a list of integers where the integers in the list are the ascii codes of the characters. Hence, the second function clause above will never match:

a.erl:6: Warning: this clause cannot match because a previous clause at line 4 always matches

But....a binary, such as <<"abc">> is NOT a string, and therefore a binary is not a shortcut for creating a list of integers.

8> "a" =:= [97].
true

Okay, you knew that. But, now:

9> "a" =:= <<"a">>.
false

10> <<"a">> =:= <<97>>.
true

11> "a" =:= <<97>>.
false

And, finally:

13> <<"abc">> =:= <<97, 98, 99>>.
true

The last example shows that specifying a double quoted string inside a binary is just a shortcut for specifying a comma separated list of integers inside a binary--however specifying a double quoted string inside a binary does not somehow convert the binary to a list.

Upvotes: 4

vkatsuba
vkatsuba

Reputation: 1459

You get this error cuz function count_occurrences/2 expect first argument list - [<<"bpacnmelhhzpygfsjoxtvkwuor">>] or "bpacnmelhhzpygfsjoxtvkwuor" but was put binary - <<"bpacnmelhhzpygfsjoxtvkwuor">>. Double check input data Line in function count/2 of module day2.erl at line 10:

1> is_list([]).
true
2> is_list("").
true
3> is_list(<<"">>).
false
4> is_list(binary_to_list(<<"">>)).
true

Upvotes: 2

Related Questions