stackcats

stackcats

How to use GenServer in leetcode?

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?

First Post!

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

Most Liked

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.

Last Post!

deadbeef

deadbeef

You’ll often see the acc last, rather than first, in your example.

Totally agree.

To provide more detail, I structured it specifically to adapt for Agent.get_and_update/5, i.e. to do this:

@spec next(n :: integer) :: integer
def next(n) do
  Agent.get_and_update(__MODULE__, RLEIterator, :rle_next, [n])
end
  • I changed the name from do_next/2 to (public) rle_next/2 to distinguish against the do_* pattern. I felt it isn’t a traditional reducer/accumulator. Felt more akin to something like Map.pop/3, where I return some value alongside the “updated” data structure in a tuple (i.e. {a, state()}
  • For args :: [term()], the state is added first, i.e. the state list needs to be the first argument passed to do_next/2/rle_next/2. So [n] effectively becomes [state, n]rle_next(state, n) (similar to the recursive case).
  • Agent.get_and_update/5 (and /3) expect the return to be {a, state()}, which is why the return is in that same ordered tuple (like mentioned above, similar to the likes of Map.pop/3)

This is also a reasonable use-case for the (often discouraged in production use) process dictionary

For sure. I chose Agent since common behaviors like get_and_update are provided “for free” and just what I was more familiar with.
But Process.put/2 for init_/1 could be cleaner since I think you can call it the same the first time and subsequent times.
Imagine you could do something like this:

defmodule RLEIterator do
  def init_(encoding), do: Process.put(__MODULE__, encoding)

  def next(n) do
    Process.get(__MODULE__)
    |> rle_next(n)
    |> then(fn {val, encoding} ->
      Process.put(__MODULE__, encoding)
      val
    end)
  end
 
  def rle_next(encoding, n)
  # ...
end

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 55125 245
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44778 311
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44265 214
New

We're in Beta

About us Mission Statement