stackcats

stackcats

900. RLE Iterator

The problem needs to keep track of the encoding list

So I used GenServer to solve this

This is the first version of the code

defmodule RLEIterator do
  use GenServer
  
  @spec init_(encoding :: [integer]) :: any
  def init_(encoding) do
    GenServer.start_link(__MODULE__, encoding, name: __MODULE__)
  end

  @spec next(n :: integer) :: integer
  def next(n) do
    GenServer.call(__MODULE__, {:next, n})
  end

  def init(encoding) do
    {:ok, encoding}
  end

  def handle_call({:next, n}, _from, state) do
    {res, new_state} = next_(n, state)
    {:reply, res, new_state}
  end

  defp next_(_n, []), do: {-1, []}
  
  defp next_(n, [ct, num | rest]) do
    if ct >= n do
      {num, [ct - n, num | rest]}
    else
      next_(n - ct, rest)
    end
  end
end

The code will be called as such:

RLEIterator.init_([3, 8, 2, 5])

param_1 = RLEIterator.next(2)

RLEIterator.init_([2, 5, 3, 8])

param_1 = RLEIterator.next(5)

I got the wrong answer.

Because there are multiple test cases with one GenServer instance.

So I changed my code

defmodule RLEIterator do
  use GenServer
  
  @spec init_(encoding :: [integer]) :: any
  def init_(encoding) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
    GenServer.call(__MODULE__, {:init, encoding})
  end

  @spec next(n :: integer) :: integer
  def next(n) do
    GenServer.call(__MODULE__, {:next, n})
  end

  def init(encoding) do
    {:ok, encoding}
  end

  def handle_call({:init, encoding}, _from, _state) do
      {:reply, [], encoding}
  end    
  def handle_call({:next, n}, _from, state) do
    {res, new_state} = next_(n, state)
    {:reply, res, new_state}
  end

  defp next_(_n, []), do: {-1, []}
  
  defp next_(n, [ct, num | rest]) do
    if ct >= n do
      {num, [ct - n, num | rest]}
    else
      next_(n - ct, rest)
    end
  end
end

I think it’s so ugly in function init_.

How to improve the code?

Showing Posts 1 to 10

Sebb

Sebb

You don’t need a GenServer for RLE. Thats just a function.
Seems like you want to do OO in Elixir.

It could look sth like

defmodule RLE do
   def encode(seq) do
   ...
   end

   def next(encoded) do
   ...
   end
end
stackcats

stackcats OP

How to do OO in Elixir such as Class static fields without GenServer or Agent?

Sebb

Sebb

I think there is a good thread here about transition from OO to Elixir, but I can’t find it.

The very short answer is: create a module with a defstruct which replaces the attributes. The functions of the module replace the methods. The functions get an instance of the struct as argument. Inheritance is replaced by aggregation.

stackcats

stackcats OP

But the signature of the function can’t be changed in leetcode. :sweat_smile:

mudasobwa

mudasobwa

Creator of Cure

This line in subsequent calls to init_/1 won’t do what you think it will. There might be only one instance with the name RLEIterator running, meaning the second and all other calls would return {:error, {:already_started, pid}} tuple and next/1 will send a message to the very first instance started. That is one of the reasons you are better to directly pattern match on the return value of GenServer.start_link/3.

{:ok, _pid} = GenServer.start_link(__MODULE__, []} # , name: __MODULE__)

You likely need an agent, keeping the map %{encoding ⇒ pid} and a bunch of anonymous instances of RLEIterators.

stackcats

stackcats OP

How to find the corresponding GenServer when calling RLEIterator.next(2)?

Agent needs the encoding list to get pid

mudasobwa

mudasobwa

Creator of Cure

Ah, indeed. next/1, accepting an integer only, won’t likely work in the concurrent environment at all.

Then what has been suggested above (no processes, no state, plain old good recursion) is the way to go.

deadbeef

deadbeef

TL;DR: I think @stackcats solution is basically spot on. Just dealing with LeetCode’s contrived problem setup/environment is awkward and basically forces anti-patterns.
Here’s my solution: https://leetcode.com/problems/rle-iterator/solutions/5024907/elixir-solved-with-recursion-state-held-in-agent/


Sorry to necropost, but came across this post while I’ve been doing LeetCode problems.

@Sebb is correct that this problem can be solved without GenServer or any “state” management. However, also pointed out, LeetCode setup the “API” in a way that we can’t just recurse/yield results. So we do need a way of storing state for subsequent calls to next/1.
IMO, it’s a bit contrived for Elixir, but I assume LeetCode has adapted this in “spirit” of other languages implementations.

I believe the problem can be “generically” (i.e. without GenServer) with something like this:

defp do_next([], _n), do: {-1, []}
defp do_next([cnt, _elm | rest], n) when cnt < n, do: do_next(rest, n - cnt)
defp do_next([cnt, elm | rest], n), do: {elm, [cnt - n, elm | rest]}

We find the value, then return the updated list, which will be used for subsequent calls.


@mudasobwa is correct about how subsequent calls to init_/1 (in current impl.) will result in {:error, {:already_started, pid}}, and that normally you’d use something like a dynamic supervisor, something to manage anonymous processes, or something. I feel test cases would normally manage resetting processes or have one “fresh” one per test case (or something). However, LeetCode provides a black box and just tells us

RLEIterator.init_/1 will be called before every test case, in which you can do some necessary initializations.

So, to me, this also feels a bit contrived, but need to adapt to the LeetCode environment. So we need a way to setup the process on the first call, then “reset” on every subsequent call.

To handle the multi-calls to init_/1, I’m sure we could do something with superviors/anonymous processes or something. Instead, I kept the process “well-known” (i.e. name: __MODULE__) and “reset” it on subsequent calls with Agent.cast/2, i.e.

@spec init_(encoding :: [integer]) :: any
def init_(encoding) do
  case GenServer.whereis(__MODULE__) do
    nil -> Agent.start_link(fn -> encoding end, name: __MODULE__)
    _ -> Agent.cast(__MODULE__, fn _ -> encoding end)
  end
end

It’s kinda hacky, IMO, but feels fine/fitting for the blackbox environment the problem suggests/provides.


I found this problem to be similar to 380. Insert Delete GetRandom O(1), which also heavily suggests the use of GenServer or the like.
Now that we have our “generic” solution and handled init_/1, we just need to wrap do_next/2 by having next/1 leverage Agent. I used it like this:

@spec next(n :: integer) :: integer
def next(n) do
  Agent.get_and_update(__MODULE__, &do_next(&1, n))
end

do_next/2’s return is already in the form of {a, state()}, so we can just call do_next/2 and pass in the current state and n.


My full solution here: https://leetcode.com/problems/rle-iterator/solutions/5024907/elixir-solved-with-recursion-state-held-in-agent/

deadbeef

deadbeef

Solution copy/pasted (in case someone doesn’t want/can’t click the link):

defmodule RLEIterator do
  use Agent

  @spec init_(encoding :: [integer]) :: any
  def init_(encoding) do
    # Kinda hacky, IMO. But feel it's a fine way to "reset" the named Agent for
    # this scenario
    case GenServer.whereis(__MODULE__) do
      nil -> Agent.start_link(fn -> encoding end, name: __MODULE__)
      _ -> Agent.cast(__MODULE__, fn _ -> encoding end)
    end
  end

  @spec next(n :: integer) :: integer
  def next(n) do
    Agent.get_and_update(__MODULE__, RLEIterator, :rle_next, [n])
  end

  @spec rle_next([integer], integer) :: {integer, [integer]}
  def rle_next([cnt, _elm | rest], n) when cnt < n, do: rle_next(rest, n - cnt)
  def rle_next([cnt, elm | rest], n), do: {elm, [cnt - n, elm | rest]}
  def rle_next([], _n), do: {-1, []}
end

# [As noted by LeetCode:]
# Your functions will be called as such:
#
# RLEIterator.init_(encoding)
# param_1 = RLEIterator.next(n)
#
# `RLEIterator.init_/1` will be called before every test case, in which you can
# do some necessary initializations.
christhekeele

christhekeele

Yep, this “reducer” pattern is pretty idiomatic Elixir, especially as you formulated it: a def xxx/1 that calls a recursive defp do_xxx/2 with an empty list as an initial accumulator, that returns the accumulator from the result of the final recursion. You’ll often see the acc last, rather than first, in your example.

This is also a reasonable use-case for the (often discouraged in production use) process dictionary (see: Process.put/2, Process.get/1, Process.delete/1).

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews