DEV Community 👩‍💻👨‍💻

Cover image for Some Elixir katas - Pt 4
Maartz
Maartz

Posted on

Some Elixir katas - Pt 4

For this kata, I'd like to focus on readability.
The kata is "Valid Parentheses", here's the gist.

Write a function that takes a string of parentheses, and determines if the order of the parentheses is valid. The function should return true if the string is valid, and false if it's invalid.

And we got some examples:

"()"              =>  true
")(()))"          =>  false
"("               =>  false
"(())((()())())"  =>  true
Enter fullscreen mode Exit fullscreen mode

So in the stub provided by Codewars, we got this.

defmodule ParenthesesValidator do
  def valid_parentheses(string) do
     # it's empty here   
  end
end
Enter fullscreen mode Exit fullscreen mode

First thing first, we need to get the value from the string parameter and transform it in a list.

One could do:

String.split("()", "")
=> ["", "(", ")", ""]
Enter fullscreen mode Exit fullscreen mode

But the returned list is dirty with the opening and closing "" empty string.

String.split("()", "", trim: true)
=> ["(", ")"]
Enter fullscreen mode Exit fullscreen mode

So we could trim it.

But we also could leverage String.graphemes/1

@spec graphemes(t()) :: [grapheme()]
delegate_to: String.Unicode.graphemes/1
Returns Unicode graphemes in the string as per Extended Grapheme Cluster
algorithm.
The algorithm is outlined in the Unicode Standard Annex #29, Unicode Text
Segmentation (https://www.unicode.org/reports/tr29/).
For details about code points and graphemes, see the String module
documentation.

Examples

iex> String.graphemes("Ńaïve")
["Ń", "a", "ï", "v", "e"]
iex> String.graphemes("\u00e9")
["é"]
iex> String.graphemes("\u0065\u0301")
["é"]
String.graphemes("(())()())")
=> ["(", "(", ")", ")", "(", ")", "(", ")", ")"]
Enter fullscreen mode Exit fullscreen mode

Good, no need to trim and no extra empty strings.

A famous cooker once said, "The key for a great dish is a good reduction, it also work in life in general".
He probably knows Enum.reduce from Elixir library.
And we're going to use it.

In fact, it's better to know what we want to achieve.
The main goal is to be able to determine if we've a closing parentheses for an opened one.

So declare an accumulator which holds the count of encountered "(" and ")".
Furthermore, we'd like to increment the count of this acc when it's a "(" and decrement when it's a ")".

And compare the accumulator to 0 because that'd say we've for each "(" a ")".

defmodule ParenthesesValidator do
  def valid_parentheses(string) do
        string 
        |> String.graphemes
        |> Enum.reduce(0, fn x, acc -> 
            case x do
                "(" -> acc + 1
                ")" -> acc - 1 
                _ -> acc
            end
        end) == 0
  end
end
Enter fullscreen mode Exit fullscreen mode

That's the what we get by using the reduce function.
Inside the modifying function, we use a case clause to increment or decrement the accumulator value.
At the end of the reduction, we compare it to 0 using == 0 to return true or false.

So... it's done right ?

Not really. I omit this test in the suit.

")("              =>  false
Enter fullscreen mode Exit fullscreen mode

But we got a closing and opening parentheses and our accumulator is equal to 0.
So in fact it's not only related to the count but also to the order of apparition.
Thus, we should not be able increment/decrement if the counter is bellow zero. Which means that a closing parentheses would put the accumulator in -1 state.
Then, we should not be able to update it. And return false.

Hopefully, guard clause are here for the rescue.
Elixir's guard clauses are directly inherited from Erlang's one.
One must know that you cannot execute function call in guard clause.

Imagine you'd like to guard on the length of a string, like this:

when String.length(string) > 10 # would fail
Enter fullscreen mode Exit fullscreen mode

But you can do this:

when byte_size(string) > 10 # 🎉
Enter fullscreen mode Exit fullscreen mode

A little [guard clause cheatsheet].(https://kapeli.com/cheat_sheets/Elixir_Guards.docset/Contents/Resources/Documents/index)

So it's good to know that we got powerful tools to describe what we want to achieve.

defmodule ParenthesesValidator do
  def valid_parentheses(string) do
        string 
        |> String.graphemes
        |> Enum.reduce(0, fn x, acc -> 
            case x do
                "(" when acc >= 0 -> acc + 1
                ")" when acc >= 0 -> acc - 1 
                _ -> acc
            end
        end) == 0
  end
end
Enter fullscreen mode Exit fullscreen mode

Here's the final version of it.
Reduce, case with guard are the bread and butter of Elixir, being able to use them will help you achieve more easily your goals.

Thanks for reading and happy coding.

Once again, a nice one:

defmodule ParenthesesValidator do
  def valid_parentheses(string) do
    case Regex.compile(string) do
      {:ok, _} -> true
      {:error, _} -> false
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Hey but it's a Regex, that's cheating 😂😂😂

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Good news! You can update to dark mode in your DEV settings.