Search code examples
algorithmfunctional-programmingelixir

Find first unique character in a string in Elixir


Given a string that contains only lowercase English letters, I am writing an Elixir function that finds the first non-repeating character in it and returns its index or else -1.

Examples:

s = "leetcode" should return 0 because "l" is the first character that does not repeat and the zero-based index is 0

s = "loveleetcode" should return 2 because v is the first character that does not repeat and the zero-based index is 2

The following is my solution so far, can you make it better or fix it?

defmodule Algos do
  def first_unique_char_index(str) do
    
    arr = String.split(str, "", trim: true)
    indexes = Enum.with_index(arr)

    first = Enum.frequencies(arr)
    |> Map.to_list
    |> Enum.sort(fn ({a,_b}, {c,_d}) -> 
      {_char1, i1} = Enum.find(indexes, (fn {x,_i} -> x == a end)) 
      {_char2, i2} = Enum.find(indexes, (fn {y,_j} -> y == c end))
      i1 <= i2 
      end)
    |> Enum.find(fn {_char, num} -> num == 1 end)

    case first do
      {char, _num} ->
        result = Enum.find(indexes, fn {x, _i} -> char == x end)
        {_letter, index} = result
        index
      nil ->
        -1
    end

  end

end

Algos.first_unique_char_index("aabcc") # returns 2
Algos.first_unique_char_index("picadillo") # returns 0
Algos.first_unique_char_index("dood") # returns -1 

As a sindenote, the problem is from the "first unique character in a string" LeetCode puzzle.


Solution

  • The below is probably the most performant solution; I decided to put it here because it reveals several interesting tricks.

    "leetcode"
    |> to_charlist()
    |> Enum.with_index() # we need index to compare by
    |> Enum.reduce(%{}, fn {e, i}, acc ->
      # trick for the future: `:many > idx` for any integer `idx` :)
      Map.update(acc, e, {e, i}, &{elem(&1, 0), :many})
    end)
    |> Enum.sort_by(&elem(elem(&1, 1), 1)) # sort to get a head
    |> case do
      [{_, {_, :many}} | _] -> "All dups"
      [{_, {result, index}} | _] -> {<<result>>, index}
      _ -> "Empty input"
    end
    #⇒ {"l", 0}