How to iterate/map over strings and keep track of their letters?

Hey all! I’m very new to Elixir and was hoping if I could some help regarding some problems that I keep facing.

Basically, I have the following list: [“eggs”, “clay”, “gegs”, “alcy”]. Now, I can initially traverse over this list using:

strings = ["eggs", "clay", "gegs", "alcy"]
Enum.flat_map(strings, fn string ->
    string 
    |> String.to_charlist
end)

Next, what I want to do is traverse over each string in this list and basically keep track of each of their letters. For example, I traverse over the word “eggs” and with each traversal (would be 4 in total), I mark an index of some array that “e” just got traversed, “g” just got traversed, and so on.

Any help would be appreciated over here.

1 Like

Hi!

I’d build a MapSet containing traversed characters and compute the size at the end (if you do not care about duplicate characters) or a Map using Map.update if you want to count characters. It really depends on what your end goal is.

That is a good idea. One additional thing I would like to ask over here is that how would you actually traverse the characters? Like when I try using Stream.flat_map or Stream.reduce, it keeps giving me the following error:

protocol Enumerable not implemented for “eggs” of type BitString

I tend to separate the iterator (or traverser) from the processor. I also use graphemes over code points since Elixir strings are UTF8 and may be 1..3 bytes and a grapheme cluster (like emoji) even longer. For example:

defmodule Iter do
  @doc """
  Traverse a list of strings and call the supplied function for each
  grapheme in each string.

  """
  @spec traverse(list(String.t), function()) :: any()
  def traverse(strings, fun, acc \\ Map.new()) when is_list(strings) and is_function(fun) do
    Enum.reduce(strings, acc, fn string, acc ->
      string
      |> String.graphemes()
      |> Enum.reduce(acc, fun)
    end)
  end

  # An example function called for each grapheme in the list of strings.
  # This example just updates the accumulator with the grapheme count.
  def fun(char, acc) do
    Map.update(acc, char, 1, fn count -> count + 1 end)
  end
end

Here’s an example of calling it:

iex> Iter.traverse ["eggs", "clay", "gegs", "alcy"], &Iter.fun/2
%{"a" => 2, "c" => 2, "e" => 2, "g" => 4, "l" => 2, "s" => 2, "y" => 2}
6 Likes

Another way to traverse a binary is with list comprehension.

iex> for <<x::utf8<-"eggs">>, reduce: %{} do acc -> Map.update(acc, <<x>>, 1, & &1 + 1) end
%{"e" => 1, "g" => 2, "s" => 1}
4 Likes

For the sake of completeness, there is also Enum.frequencies/1.

["eggs", "clay", "gegs", "alcy"] |> Enum.join() |> String.graphemes() |> Enum.frequencies()
#⇒ %{"a" => 2, "c" => 2, "e" => 2, "g" => 4, "l" => 2, "s" => 2, "y" => 2}
6 Likes