Bhavika Ramesh
Bhavika Ramesh

Reputation: 54

How do I overcome Protocol.UndefinedError in elixir?

I am working on writing a code for cows and bulls game. Here is my code so far.

defmodule Demo do

    def game() do
        guess = 1234
        secret = Enum.take_random(1..9, 4)
        count(guess, secret)
    end

    defp count(guess, secret) do
        Enum.zip(guess, secret)
                |> Enum.reduce({0, 0}, fn {g, s}, {c, b} ->
                        cond do
                            g == s -> {c, b + 1}
                            g in secret -> {c + 1, b}
                            true  -> {c, b}
                        end
                end)
    end 
end

When I am running the file using IO.puts Demo.game I am getting the following error

(Protocol.UndefinedError) protocol Enumerable not implemented for 1234
(elixir) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir) lib/enum.ex:141: Enumerable.reduce/3
(stdlib) lists.erl:1338: :lists.foreach/2
(elixir) lib/stream.ex:1157: Stream.do_zip/3
(elixir) lib/enum.ex:2802: Enum.zip/1
prog.ex:10: Demo.count/2
prog.ex:20: (file)

Help me understand where I went wrong and how to clear the error

Edit:

I changed the cond condition to how you suggested. But instead of using Integer.digits I converted the randomly generated list to string to make it enumerable but I am facing an error. The code looks like this.

defmodule Demo do

def game() do
    guess = Integer.digits(1234)
    secret = Enum.take_random(1..9, 4) |> Enum.map(&to_string/1)
    numOfBullsCows(guess, secret)
end


def numOfBullsCows(guess, secret) do
    Enum.zip(guess, secret)
            |> Enum.reduce({0, 0}, fn 
                    {g, g}, {c, b} -> {c, b + 1}
                    {g, _}, {c, b} when g in secret -> {c + 1, b}
                    _, {c, b}-> {c, b}
            end)
end

end

The error:

(ArgumentError) invalid args for operator "in", it expects a compile-time proper list or compile-time range on the right side when used in guard expressions, got: secret
(elixir) expanding macro: Kernel.in/2
prog.ex:14: Demo.numOfBullsCows/2
(elixir) expanding macro: Kernel.|>/2
prog.ex:12: Demo.numOfBullsCows/2

I have also tried it in another way where I created two seperate functions to check for duplicates and then to generate a random number with no duplicate digit in it.

defmodule Main do
  def noDuplicates(num) do
    num_li = Integer.digits(num)
    length(num_li) == length(Enum.uniq(num_li))
  end

  def generateNum() do
    num = Enum.random(1000, 9999)
    if noDuplicates(num) == true do
        IO.puts(num)
    else
        generateNum()
    end
  end
end

But I am getting an error like this

(UndefinedFunctionError) function Enum.random/2 is undefined or private. Did you mean one of:

  * random/1

(elixir) Enum.random(1000, 9999)
prog.ex:8: Main.generateNum/0
prog.ex:17: (file)
(elixir) lib/code.ex:767: Code.require_file/2
        

      

Upvotes: 0

Views: 346

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Enum.zip/2 expects two enumerables as arguments and you pass 1234, [3, 5, 1, 6] or like (the latter is randomly generated secret, the list of size 4) there.

Use Integer.digits/2 to split the integer into digits so that Enum.zip/2 was happy.


Sidenote: instead of using cond/1, it’s more idiomatic to use several function clauses

...
|> Enum.reduce({0, 0}, fn
 # ⇓  ⇓ same name matches when they are equal
  {g, g}, {c, b} -> {c, b + 1}
 #               ⇓⇓⇓⇓ guard   
  {g, _}, {c, b} when g in secret -> {c + 1, b}
 # catch-all
  _, {c, b}  -> {c, b}
end)

Upvotes: 2

Related Questions