kimc0de

kimc0de

Check if array contains nearby duplicates

Hi all, I’m trying to solve this leetcode question in elixir. https://leetcode.com/problems/contains-duplicate-ii/

Given an integer array nums and an integer k , return true if there are two distinct indices i and j in the array such that nums[i] == nums[j] and abs(i - j) <= k .

The java solution is available but it was quite complicated for me to convert to elixir.. Could anyone give me some hints or explanations on how to solve it in elixir?

Marked As Solved

Eiji

Eiji

Isn’t your solution a bit complicated? :sweat_smile:

Based on your example I wrote my own solution using pattern-matching, [head | tail] notation and recursion as suggested by @lud. Since all checks are guard and the list is iterated only once my version should be much more faster.

First of all we can add index and perform all checks in one call. Secondly we do not need to store old index that failed our checks. Only noticing it allows us to much reduce out code.

defmodule Example do
  # function head for shared default argument values
  def sample(nums, k, acc \\ %{}, current_index \\ 1)

  # when check failed for all nums return false
  def sample([], _k, _acc, _current_index), do: false

  # check if num is in acc and then
  # check if abs of expression
  # current index - last saved index for same num
  # is less or equal to k
  def sample([num | _nums], k, acc, current_index)
      when is_map_key(acc, num) and abs(current_index - :erlang.map_get(num, acc)) <= k do
    true
  end

  # otherwise save current index in acc for this num
  # and continue checking rest nums
  def sample([num | nums], k, acc, current_index) do
    sample(nums, k, Map.put(acc, num, current_index), current_index + 1)
  end
end

false = [1, 2, 3, 4, 2] |> Example.sample(1)
false = [1, 2, 3, 4, 2] |> Example.sample(2)
true = [1, 2, 3, 4, 2] |> Example.sample(3)
false = [1, 2, 3, 4] |> Example.sample(4)
true = [1, 0, 1, 1] |> Example.sample(1)

Helpful resources:

  1. Elixir official page |> Getting Started |> Recursion
  2. Elixir documentation |> Pages |> Patterns and Guards
  3. :erlang.map_get/2
  4. Kernel.abs/1
  5. Kernel.is_map_key/2
  6. Map.put/3

Also Liked

al2o3cr

al2o3cr

One approach I’ve found useful for problems like this is to look at the requirements:

  • this needs to loop over each element of the input
  • needs to maintain state between elements of the input to track what’s already been seen
  • can stop early (if a duplicate is detected)

A tool that matches these requirements is Enum.reduce_while/3, a skeleton of a use for it would look something like:

Enum.reduce_while(nums, SOME_STATE_NOT_DECIDED_YET, fn num, STATE ->
  if SHOULD_STOP?(num, STATE) do
    {:halt, true}
  else
    {:cont, UPDATE_STATE(num, STATE)}
  end
end)
|> case do
  true -> true
  _STATE_DONT_CARE -> false
end

The bits in all-caps are going to vary based on the problem, but this is the general idea. The final case handles the situation when the reduce_while makes it to the end and returns whatever shape the “state” is.


A simple example for using this approach is “Contains Duplicate” (the prequel to “Contains Duplicate II”). A solution for that might look like:

Enum.reduce_while(nums, MapSet.new(), fn num, seen ->
  if MapSet.member?(seen, num) do
    {:halt, true}
  else
    {:cont, MapSet.put(seen, num)}
  end
end)
|> case do
  true -> true
  _ -> false
end

In this case the “state” is a single MapSet, allowing for quick checking of “is this new num one that’s already been seen?”


For “Contains Duplicate II”, the set of “seen” elements needs to be restricted to a maximum size k. When a k+1th element is seen, the oldest one needs to be dropped.

This isn’t possible with MapSet alone; it does not provide guarantees about element ordering. To handle it, we need a second piece of “state”: a queue of numbers that can answer “what element did we see k elements ago?” efficiently.

The :queue module is a decent starting point with better performance characteristics than a plain List when values are removed from the opposite end.

Another useful piece of information: the size of a MapSet is efficiently computable, especially compared to :queue.len which is O(k).

A solution for part 2 might look like (I have not run this code, beware):

Enum.reduce_while(nums, {MapSet.new(), :queue.new()}, fn num, {seen, last_seen} ->
  cond do
    MapSet.member?(seen, num) ->
      {:halt, true}

    MapSet.size(seen) < k ->
      {:cont, {MapSet.put(seen, num), :queue.in(num, last_seen)}

    true ->
      {{:value, very_last_seen}, last_seen} = :queue.out(last_seen)

      seen = MapSet.delete(seen, very_last_seen)

      {:cont, {MapSet.put(seen, num), :queue.in(num, last_seen)}    
  end
end)
|> case do
  true -> true
  _ -> false
end

The if in the skeleton has split into a 3-way cond:

  • if num is in seen, we’re done here
  • if seen is smaller than the maximum, record num and go onto the next one
  • otherwise remove the oldest number from seen and carry on

I’d likely extract parts of this to a function, but I kept everything inline for this discussion to show the similarities.

lud

lud

In elixir you can pattern match on a list:

def myfun([first_item, second_item | tail]) do

end

Then, you would use recursion to traverse the whole list.

Does it help?

viniarck

viniarck

You could group values by their indexes and for each grouped values, recursively iterate with a look ahead pointer on a list that has at least two elements comparing if the indexes abs diff is <= k, if you do all of this lazily and then try to take at least 1 and if it’s not an empty list then you’ve found one pair of indexes.

I think something like this would do it:

defmodule Solution do
  defp has_index_diff_le?(values, k) do
    case values do
      [index1, index2 | rest] ->
        case abs(index2 - index1) <= k do
          true -> true
          false -> has_index_diff_le?([index2 | rest], k)
        end

      _ ->
        false
    end
  end

  def contains_dup?(values, k) do
    result =
      values
      |> Stream.with_index()
      |> Enum.group_by(fn x -> elem(x, 0) end, fn x -> elem(x, 1) end)
      |> Map.values()
      |> Stream.filter(&match?([_head | _tail], &1))
      |> Stream.map(fn vals -> has_index_diff_le?(vals, k) end)
      |> Stream.drop_while(&(&1 == false))
      |> Stream.take(1)
      |> Enum.to_list()

    case result do
      [] -> false
      [_] -> true
    end
  end
end

false = [1, 2, 3, 4, 2] |> Solution.contains_dup?(1)
false = [1, 2, 3, 4, 2] |> Solution.contains_dup?(2)
true = [1, 2, 3, 4, 2] |> Solution.contains_dup?(3)
false = [1, 2, 3, 4] |> Solution.contains_dup?(4)
true = [1, 0, 1, 1] |> Solution.contains_dup?(1)

Where Next?

Popular in Questions Top

_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

Other popular topics Top

AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New

We're in Beta

About us Mission Statement