I am trying to write an anagram checker in Elixir. It takes 2 words, the first one is a reference, the second is to be tested as a possible anagram of the first.
I am trying to write it with recursion and pattern matching. I get an error about using the in
operator in a guard clause:
(ArgumentError) invalid args for operator in, it expects a compile time list or range on the right side when used in guard expressions
I don't know what to do to fix it. Here is the code (error is in 4th definition):
defmodule MyAnagram do
def anagram?([], []), do: true
def anagram?([], word) do
IO.puts 'Not an anagram, the reference word does not contain enough letters'
false
end
def anagram?(reference, []) do
IO.puts 'Not an anagram, some letters remain in the reference word'
false
end
def anagram?(reference, [head | tail]) when head in reference do
anagram?(reference - head, tail)
end
def anagram?(_, [head | _]) do
IO.puts 'Not an anagram, #{head} is not in the reference word.'
false
end
end
This is caused by the following code (as you identified):
def anagram?(reference, [head | tail]) when head in reference do
anagram?(reference - head, tail)
end
You can find the definition of the in
macro in the source code, but I have copied it here for convenience - it also contains the following in the documentation:
Guards
The
in
operator can be used in guard clauses as long as the right-hand side is a range or a list. In such cases, Elixir will expand the operator to a valid guard expression. For example:when x in [1, 2, 3]
translates to:
when x === 1 or x === 2 or x === 3
The code that defines the macro:
defmacro left in right do
in_module? = (__CALLER__.context == nil)
right = case bootstraped?(Macro) and not in_module? do
true -> Macro.expand(right, __CALLER__)
false -> right
end
case right do
_ when in_module? ->
quote do: Elixir.Enum.member?(unquote(right), unquote(left))
[] ->
false
[h|t] ->
:lists.foldr(fn x, acc ->
quote do
unquote(comp(left, x)) or unquote(acc)
end
end, comp(left, h), t)
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]} ->
in_range(left, Macro.expand(first, __CALLER__), Macro.expand(last, __CALLER__))
_ ->
raise ArgumentError, <<"invalid args for operator in, it expects a compile time list ",
"or range on the right side when used in guard expressions, got: ",
Macro.to_string(right) :: binary>>
end
end
Your code block is hitting the last part in the case statement because it can't be guaranteed at compile time that your variable reference
is of type list
(or range
.)
You can see what value is being passed to the macro by calling:
iex(2)> quote do: head in reference
{:in, [context: Elixir, import: Kernel],
[{:head, [], Elixir}, {:reference, [], Elixir}]}
Here, the atom :reference
is being passed to the in
macro, which doesn't match any of the previous clauses so it falls through to the _
clause (which raises the error.)
To fix this, you will need to combine your last two clauses into one function:
def anagram?(reference, [head | tail]) do
case head in reference do
false ->
IO.puts 'Not an anagram, #{head} is not in the reference word.'
false
true ->
anagram?(reference - head, tail)
end
end
It is also worth noting that you probably want to use "strings"
instead of 'char_lists'
http://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#char-lists
The other thing is that calling reference - head
won't work (it will raise an ArithmeticError
). You might want to look at List.delete/2 to remove an item from a list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With