Isogram - Exercism Elixir Solution & Tutorial


Percy Grunwald's Profile Picture

Written by Percy Grunwald

— Last Updated February 22, 2019

Today’s challenge was to implement a function to check if a string is an isogram or “nonpattern word” – a word or phrase that has no repeated letters.

Live solution video

This is my daily live stream video solution to this problem.

Explanation of the solution

My approach to solving this problem was:

  1. Split the string into a list of all the characters (excluding hyphens and spaces)
  2. Create a second list containing only the unique characters of the original list
  3. Compare the lengths of the two lists: if the list of unique characters is the same length as the original list, then none of the characters are repeated and the original string is an isogram

How to exclude hyphens and spaces from a string in Elixir

I think using String.replace/4 is the best way for us to remove characters from a string. Here’s an example of how you could use the function to remove hyphens and spaces from a string:

iex> "a-b c-c" |> String.replace(~r/[- ]/, "")
"abcc"

In the example above, we use ~r, know as sigil_r, to create a regex to match hyphens and spaces only /[- ]/ and replace any match with an empty string "".

How to split a string into its characters in Elixir

After removing unwanted characters, we need to split the string into a list of the remaining characters. There are a number of ways to split a string into its characters in Elixir, here are the functions I can think of:

  1. String.split/3
  2. String.graphemes/1 - forgot about this function while recording the video!
  3. String.to_charlist/1

Here’s an example of how you could use each one:

iex> "abcc" |> String.split("", trim: true)
["a", "b", "c", "c"]

iex> "abcc" |> String.graphemes()
["a", "b", "c", "c"]

iex> "abcc" |> String.to_charlist() |> IO.inspect(charlists: :as_lists)
[97, 98, 99, 99]
'abcc'

As you can see, they all basically do the same thing (except for the string/charlist distinction). I opted for String.split/3 simply because that was top of mind for me when solving the problem.

How to get only unique items from a list in Elixir

The Enum.uniq function is exactly what we need – it removes all duplicated items in a list, leaving only the unique entries:

iex> ["a", "b", "c", "c"] |> Enum.uniq()
["a", "b", "c"]

How to compare the lengths of two lists in Elixir

The final thing we need to do is compare the lengths of the original list of characters and the unique list of characters. To get the length of a list, we can use either:

  1. Kernel.length/1
  2. Enum.count/1

Both do the same thing:

iex> ["a", "b", "c"] |> Kernel.length()
3

iex> ["a", "b", "c"] |> Enum.count()
3

I personally prefer Kernel.length/1 mainly because the idea of “list length” is pretty ubiquitous and in most languages the number of items in a list is called “length”. It just makes the code that little bit easier for a human to parse.

Comparing the lengths is just an integer comparison, so == works fine:

iex> string_split = "abcc" |> String.split("", trim: true)
["a", "b", "c", "c"]

iex> string_split_unique = Enum.uniq(string_split)
["a", "b", "c"]

iex> Kernel.length(string_split) == Kernel.length(string_split_unique)
false

Full solution in text form

Here’s the full solution in text form, in case you want to look over the final product:

defmodule Isogram do
  @doc """
  Determines if a word or sentence is an isogram
  """
  @spec isogram?(String.t()) :: boolean
  def isogram?(sentence) do
    string_split =
      sentence
      |> String.replace(~r/[- ]/, "")
      |> String.downcase()
      |> String.split("", trim: true)

    string_split_unique = Enum.uniq(string_split)

    Kernel.length(string_split) == Kernel.length(string_split_unique)
  end
end

Comments