small medium large xlarge

Dave_gnome_head_isolated_pragsmall
15 Jul 2013, 03:42
Dave Thomas (367 posts)
  • Alter the code so that successive ticks are sent to each registered client (so the first goes to the first client, the second the next client, and so on). Once the last client receives a tick, it starts back at the first. The solution should deal with new clients being added at any time.
Generic-user-small
08 Jan 2014, 07:23
Eric Liaw (3 posts)

Don’t really like the default definition of generator when the inputs are empty, but seemed to cause the least amount of duplicate code. Also, I don’t like that I used the if inside the main generator method, wasn’t sure a clean way around it though.

defmodule Tick do
  @interval 2000 # 2 seconds
  
  @name :ticker
  
  def start do
    pid = spawn(__MODULE__, :generator, [[]])
    :global.register_name(@name, pid)
  end
  
  def register(client_pid) do
    :global.whereis_name(@name) <- { :register, client_pid }
  end
  
  def generator(clients) do
    generator(clients, [], nil)
  end
  
  def generator([], [], nil) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        generator([pid], [], pid)
    after
      @interval ->
        IO.puts "tick"
        generator([], [], nil)
    end
  end
  
  def generator(active_clients, pending_clients, first_client) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        generator(active_clients, pending_clients ++ [pid], first_client)
    after
      @interval ->
        IO.puts "tick"
        [next_client | remaining_clients] = active_clients
        next_client <- {:tick}
        if next_client == first_client do
          generator(remaining_clients ++ pending_clients ++ [next_client], [], first_client)
        else
          generator(remaining_clients ++ [next_client], pending_clients, first_client)
        end
    end
  end
end

defmodule Client do
  def start do
    pid = spawn(__MODULE__, :receiver, [])
    Tick.register(pid)
  end

  def receiver do
    receive do
      { :tick } ->
        IO.puts "tock in client"
        receiver
    end
  end
end
Webcam_pragsmall
06 Mar 2014, 00:32
Andrea Bernardo Ciddio (2 posts)

What about a helper notify function:

defmodule CycleTick do
  @interval 2000
  @name :ticker

  def start do
    pid = spawn __MODULE__, :generator, [[], []]
    :global.register_name @name, pid
  end

  def register(client_pid) do
    send :global.whereis_name(@name), { :register, client_pid }
  end

  def generator(clients, waiting_clients) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        generator [pid | clients], waiting_clients
      after @interval ->
        IO.puts "tick"
        waiting_clients = notify(clients, waiting_clients)
        generator(clients, waiting_clients)
    end
  end

  def notify([], []), do: []
  def notify(clients, []), do: notify(clients, clients)

  def notify(_clients, [next_client | waiting_clients]) do
    send next_client, { :tick }
    waiting_clients
  end

end

defmodule Client do
  def start do
    pid = spawn __MODULE__, :receiver, []
    CycleTick.register pid
  end

  def receiver do
    receive do
      { :tick } ->
        IO.puts "tock in client"
        receiver
    end
  end
end
200-g_pragsmall
09 Oct 2014, 19:23
Aleksey Gureiev (11 posts)

My take:

defmodule Ticker do

  @interval 2000

  @name :ticker

  def start do
    pid = spawn __MODULE__, :generator, [[]]
    :global.register_name @name, pid
  end

  def register(client_pid) do
    send :global.whereis_name(@name), { :register, client_pid }
  end

  def generator(clients) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        generator([pid|clients])

    after
      @interval ->
        case clients do
          [] ->
            generator clients

          [client|rest] ->
            IO.puts "tick to #{inspect client}"
            send client, { :tick }
            generator :lists.append(rest, [ client ])
        end
    end
  end

end

defmodule Client do

  def start do
    pid = spawn __MODULE__, :receiver, []
    Ticker.register pid
  end

  def receiver do
    receive do
      { :tick } ->
        IO.puts "tock in client"
        receiver
    end
  end

end
Generic-user-small
11 Jan 2015, 12:21
Pierre Sugar (56 posts)
defmodule Ticker do

  @interval 2000
  @name     :ticker

  def start do
    pid = spawn(__MODULE__, :generator, [[], 0])
    :global.register_name(@name, pid)
  end

  def register(client_pid) do
    send :global.whereis_name(@name), { :register, client_pid }
  end

  def generator(clients, next_client_number) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        generator([pid|clients], next_client_number)
    after
      @interval ->
        IO.puts "tick"
        send_to_client(clients, next_client_number)
        generator(clients, next_client_number + 1)
    end
  end

  defp send_to_client(clients, next_client_number) when length(clients) > 0 do
    { :ok, client } = clients 
                      |> Enum.fetch(rem(next_client_number, length(clients)))
    send client, { :tick }
  end
  defp send_to_client([], _), do: nil
end

defmodule Client do

  def start do
    pid = spawn(__MODULE__, :receiver, [])
    Ticker.register(pid)
  end

  def receiver do
    receive do
      { :tick } ->
        IO.puts "tock in client"
        receiver
    end
  end
end
Generic-user-small
12 Jun 2015, 06:29
Jingjing Duan (3 posts)

Here’s my take:

defmodule Ticker do
  @interval 2000 # 2 seconds
  @name :ticker

  def start do
    pid = spawn(__MODULE__, :generator, [[]])
    :global.register_name(@name, pid)
  end

  def register(client_pid) do
    send :global.whereis_name(@name), {:register, client_pid}
  end

  def generator(clients) do
    receive do
      {:register, pid} ->
        IO.puts "registering #{inspect pid}"
        generator(List.insert_at(clients, -1, pid))
    after
      @interval ->
        IO.puts "tick"
        case clients do
        [head | tail] ->
          send head, {:tick}
          generator(List.insert_at(tail, -1, head))
        _ ->
          generator(clients)
        end
    end
  end
end

defmodule Client do
  def start(name) do
    pid = spawn(__MODULE__, :receive_messages, [name])
    Ticker.register(pid)
  end

  def receive_messages(name) do
    receive do
      {:tick} ->
        IO.puts "tock in #{name}"
        receive_messages(name)
    end
  end
end
Generic-user-small
07 Aug 2015, 23:36
Stefan Chrobot (13 posts)

Here’s my solution. I used a small improvement for sending of the ticks. I use a queue for handling the clients. I moved it to a separate module to implement an optimized version (avoiding ++):

defmodule Utils do
  def current_time_string do
    {_date, {h, m, s}} = :calendar.local_time
    :io_lib.format("~2..0B:~2..0B:~2..0B", [h, m, s])
  end
end

# idea by Peter Hamilton
# https://groups.google.com/d/msg/elixir-lang-talk/tJnt6TWqV-Q/v66Hogks3JcJ
defmodule Queue do
  def new do
    {[], []}
  end

  def empty?({[], []}), do: true
  def empty?(_), do: false

  def enqueue({natural_items, reversed_items}, item) do
    {natural_items, [item|reversed_items]}
  end

  def dequeue({natural_items, reversed_items}) do
    case natural_items do
      [head|tail] ->
        {head, {tail, reversed_items}}
      [] ->
        dequeue {Enum.reverse(reversed_items), []}
    end
  end
end

defmodule Ticker do
  @interval 2000
  @name   :ticker

  def start do
    ticker_pid = spawn(__MODULE__, :generator, [Queue.new])
    :global.register_name(@name, ticker_pid)
    :timer.send_interval(@interval, ticker_pid, {:do_tick})
  end
  
  def register(client_pid) do
    ticker_pid = :global.whereis_name @name
    send ticker_pid, {:register, client_pid}
  end
  
  def generator(client_pids) do
    receive do
      {:register, new_client_pid} ->
        IO.puts "registering #{inspect new_client_pid}"
        generator Queue.enqueue(client_pids, new_client_pid)
      {:do_tick} ->
        if Queue.empty?(client_pids) do
          IO.puts "#{Utils.current_time_string} - <no clients> - tick"
          generator client_pids
        else
          {next_client_pid, client_pids} = Queue.dequeue client_pids
          IO.puts "#{Utils.current_time_string} - #{inspect next_client_pid} - tick"
          send next_client_pid, {:tick}
          generator Queue.enqueue(client_pids, next_client_pid)
        end
    end
  end
end

defmodule Client do
  def start do
    client_pid = spawn(__MODULE__, :receiver, [])
    Ticker.register client_pid
  end
  
  def receiver do
    receive do
      {:tick} ->
        IO.puts "#{Utils.current_time_string} - #{inspect self} - tock"
        receiver
    end
  end
end
Generic-user-small
12 Aug 2015, 16:47
Jim Kane (6 posts)

I started out with a solution similar to Pierre’s incrementing counter, but it didn’t feel very recursive. Then I tried passing the full client list and the “remaining clients” (clients who haven’t received a tick on this iteration), which felt much more idiomatic. I think this has been my favorite exercise so far.

defmodule Ticker do

  @interval 2000 # 2 seconds
  @name     :ticker

  def start do
    pid = spawn(__MODULE__, :generator, [[], []])
    :global.register_name(@name, pid)
  end

  def register(client_pid) do
    send :global.whereis_name(@name), { :register, client_pid }
  end

  def generator(clients, remaining_clients) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        generator([pid|clients], remaining_clients)
    after @interval ->
      IO.puts "tick"
      do_tick(clients, remaining_clients)
    end
  end

  defp do_tick([], []) do
    generator([], [])
  end

  defp do_tick([ _head | _tail ] = clients, []) do
    do_tick(clients, clients)
  end

  defp do_tick(clients, remaining_clients) do
    [ client | remaining_clients ] = remaining_clients
    send client, { :tick }
    generator(clients, remaining_clients)
  end

end

defmodule Client do

  def start do
    pid = spawn(__MODULE__, :receiver, [])
    Ticker.register(pid)
  end

  def receiver do
    receive do
      { :tick } ->
        IO.puts "tock in client"
        receiver
    end
  end
end
Generic-user-small
18 Nov 2015, 04:37
Aaron Strick (1 post)

I love the Jim Kane method. Very clean.

I’m not sure if it’s more idiomatic, I chose to reimpliment it that way, but I did make one change:

instead of

defp do_tick([ _head | _tail ] = clients, []) do

I preferred

defp do_tick(clients, []) when (length clients) > 0

Not sure what is more idiomatic for Elixir.

Generic-user-small
13 Dec 2015, 17:59
Charles Okwuagwu (4 posts)

My Attempt with added stop function

defmodule TickServer do
  @name :ticker_server
  @interval 2000

  def start do
    sid = spawn(__MODULE__, :generator, [[],[]])
    :global.register_name(@name, sid)
    {:ok, tid} = :timer.send_interval(@interval, sid, {:tick})
    Process.put(:timer_id, tid)
  end

  def stop do
    :timer.cancel Process.get(:timer_id)
    send :global.whereis_name(@name), {:stop}
  end

  def reg(client_pid) do
    send :global.whereis_name(@name), {:reg, client_pid}
  end

  def un_reg(client_pid) do
    send :global.whereis_name(@name), {:un_reg, client_pid}
  end

  defp time_stamp do
    {_date, {h, m, s}} = :calendar.local_time
    :io_lib.format("~2..0B:~2..0B:~2..0B", [h, m, s])
  end

  def generator(remaining, clients) do
    receive do
      {:stop} ->
        clients |> Enum.each(fn(cid) -> send(cid, {:stop}) end)
        IO.puts "#{inspect __MODULE__} stoped"
      {:reg, client_pid} ->
        IO.puts "Added Client: #{inspect client_pid}"
        generator(remaining, List.insert_at(clients, -1, client_pid))
      {:un_reg, client_pid} ->
        IO.puts "Removed Client: #{inspect client_pid}"
        generator(List.delete(remaining, client_pid), List.delete(clients, client_pid))
      {:tick} when {remaining, clients} == {[],[]} ->
        #IO.puts "#{inspect time_stamp} - tick - <no clients>"
        generator(remaining, clients)
      {:tick} when remaining == [] ->
        [next|remaining] = clients
        send next, {:tick,  "#{inspect time_stamp} - tick - #{inspect next}"}
        generator(remaining, clients)
      {:tick} ->
        [next|remaining] = remaining
        send next, {:tick,  "#{inspect time_stamp} - tick - #{inspect next}"}
        generator(remaining, clients)
    end
  end
end

defmodule Client do
  def start do
    cid = spawn(__MODULE__, :receiver, [])
    TickServer.reg(cid)
  end

  def receiver do
    receive do
      {:stop} ->
        IO.puts "#{inspect self} stoped"
      { :tick , msg} ->
        IO.puts msg
        receiver
    end
  end
end
Generic-user-small
26 Feb 2016, 18:54
Artem Chernayk (1 post)

Here is my simple implementation. I found this could be done by only modifying the generator after.

def generator(clients) do
  receive do
    {:register, pid} ->
      IO.puts "registering #{inspect pid}"
      generator([pid | clients])
  after
    @interval ->
      IO.puts "tick"
      if Enum.empty?(clients) do
        [head | tail] = clients
        send head, {:tick}
        generator(tail ++ [head])
      else
        generator(clients)
      end
  end
end
Generic-user-small
03 Mar 2016, 10:36
Stefan Houtzager (8 posts)

I liked Jingjing Duan’s solution the most. Making it slightly more readable and only modifying the after block in the original code from Dave Thomas I get

after
      @interval ->
        IO.puts "tick"
        case clients do
        [head | tail] ->
          send head, {:tick}
          generator([tail | head])
        _ ->
          generator(clients)
        end
Generic-user-small
30 Mar 2016, 15:47
Andrea Longhi (2 posts)
defmodule Ticker do
  @interval 2000
  @name :ticker

  def start do
    pid = spawn __MODULE__, :generator, [{[], 0}]
    :global.register_name @name, pid
  end

  def register client_pid do
    send :global.whereis_name(@name), {:register, client_pid}
  end

  def generator {clients, counter} do
    receive do
      {:register, pid} ->
        IO.puts "registering a new pid #{inspect pid}"
        clients = [pid|clients]
      after @interval ->
        {clients, counter} = send_tick clients, counter
    end
    generator {clients, counter}
  end

  def send_tick([], counter), do: {[], counter}

  def send_tick clients, counter do
    IO.puts "tick"
    client  = Enum.at clients, rem(counter, length(clients))
    send client, {:tick}
    {clients, counter+1}
  end
end


defmodule Client do
  def start do
    pid = spawn_link __MODULE__, :receiver, []
  end

  def receiver do
    receive do
      {:tick} ->
        IO.puts "tock in client #{inspect self}"
    end
    receiver
  end
end
Generic-user-small
09 May 2016, 11:11
Patrick McDonnell (1 post)

Jingjing Duan’s solution looks and works well for me. I liked the look of Stefan Houtzager’s for readability, but the solution did not work for me. I think the syntax

[tail | head]

Is not correct for placing the head at the back of the list, though I can see why it is tempting.

Here is the (current) source for insert_at from Elixir source, just for rerference:


  # insert_at

  defp do_insert_at([], _index, value) do
    [value]
  end

  defp do_insert_at(list, index, value) when index <= 0 do
    [value | list]
  end

  defp do_insert_at([h | t], index, value) do
    [h | do_insert_at(t, index - 1, value)]
  end

So we never actually do a “single item placement” at the back of the list, rather we recurse to build up the list.

You must be logged in to comment