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
So in the stub provided by Codewars, we got this.
defmodule ParenthesesValidator do
def valid_parentheses(string) do
# it's empty here
end
end
First thing first, we need to get the value from the string
parameter and transform it in a list.
One could do:
String.split("()", "")
=> ["", "(", ")", ""]
But the returned list is dirty with the opening and closing ""
empty string.
String.split("()", "", trim: true)
=> ["(", ")"]
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("(())()())")
=> ["(", "(", ")", ")", "(", ")", "(", ")", ")"]
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
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
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
But you can do this:
when byte_size(string) > 10 # 🎉
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
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
Hey but it's a Regex, that's cheating 😂😂😂
Top comments (0)