small medium large xlarge

Dave_gnome_head_isolated_pragsmall
15 Jul 2013, 03:42
Dave Thomas (381 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 (57 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.

Img_2196_pragsmall
07 Jun 2016, 10:56
Diogo Neves (12 posts)

I decided to access the list directly just for the sake of trying.
The other solution would be to keep a list of remaining clients to tick and refill when empty. I didn’t manage to finish this one without duplicating the receive code, any ideas?

I also added a shutdown:

defmodule Ticker do

  @interval  2000  # 2 seconds
  @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 shutdown do
    send :global.whereis_name(@name), {:shutdown}
  end

  def generator(clients, i) do
    receive do
      {:register, pid} ->
        IO.puts "registering #{inspect pid}"
        generator([pid|clients], i)
      {:shutdown} ->
        IO.puts "shutting down the ticker"
        :global.unregister_name(@name)
    after
      @interval ->
        IO.puts "tick"
        if !Enum.empty?(clients) do
          client = Enum.at(clients, rem(i, Enum.count(clients)))
          send client, {:tick, i}
        end
        generator(clients, i + 1)
    end
  end

end

defmodule Client do

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

  def receiver do
    receive do
      {:tick, i} ->
        IO.puts "tock in the client, #{inspect i}"
        receiver
    end
  end
end
Generic-user-small
18 Jun 2016, 14:57
Mark Wilbur (2 posts)

Fun, fun! Here’s mine:

defmodule Ticker do
  @interval 4000
  @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 ->
      IO.puts "tick"
      case clients do
        [] -> IO.puts "empty list"
          generator []
        [client | rest] -> 
          send client, { :tick, :calendar.universal_time }
          generator(rest ++ [client])
      end
    end
  end
end

defmodule Client do
  
  def start do
    pid = spawn(__MODULE__, :receiver, [])
    Ticker.register(pid)
  end
  
  def receiver(last\\nil) do
    receive do
      { :tick, time } ->
        IO.puts "tock in client at #{inspect time}, last was #{inspect last}"
        receiver(time)
      { :die } -> IO.puts "aaaauggghhh..."
    end
  end
end

The alternation seemed pretty straight forward, but I don’t know how reliable :calendar.universal_time is. Also, I’m not quite sure how to use the :die listener I made for the client.

Generic-user-small
16 Jul 2016, 14:15
Nathan Hessler (10 posts)

I’m not super happy with my rotate function. feel like this could be done better, but otherwise pretty happy with the resulting code.

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([pid | clients])
    after
      @interval ->
        IO.puts "tick"
        clients |> List.last |> notify_client
        clients |> rotate_clients |> generator
    end
  end

  defp notify_client(nil), do: nil
  defp notify_client(pid), do: send(pid, { :tick })

  defp rotate_clients([]), do: []
  defp rotate_clients(clients) do
    head = List.last(clients)
    tail = List.delete_at(clients, length(clients) - 1)
    [ head | tail ]
  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 Aug 2016, 01:27
Eric Lehner (5 posts)

Ring that starts with the first process looping ticks to itself.

When a new client connects, the current process hands it’s current receiving ticker process over to the client, establishes the client as it’s receiver, and stops listening for new connections. The client then takes over as the listener, waiting to do the same as the process before it. This allows the chain to grow from the end and always loop back to the beginning, and each process only needs to know about the next pid. No process ever needs to know anything else about the chain.

Not really safe, as shutting down the final process will stop both the addition of new processes and the tick chain, but it’s pretty stable otherwise.

defmodule RingMasterClient do
  @interval 2000 # 2 seconds
  @name :ring

  def start do
    if :global.whereis_name(@name) != :undefined do
      # send the current listener your connect pid if the listener
      ticker_pid = spawn(__MODULE__, :ticker, [])
      connect_pid = spawn(__MODULE__, :connect, [ticker_pid])
      send(:global.whereis_name(@name), { :connect, connect_pid, ticker_pid })
    else
      # become the new listener right away and start a ticker that talks
      # to itself
      ticker_pid = spawn(__MODULE__, :ticker, [:initial])
      connector_pid = spawn(__MODULE__, :connector, [ticker_pid, ticker_pid])
      :global.register_name(@name, connector_pid)
    end
  end

  # Listens for connection ot the ring
  def connect(ticker_pid) do
    receive do
      { :connected, new_receiver_pid } ->
        send(ticker_pid, { :update, new_receiver_pid })
        # removes the old pid listener and established a new connector
        connector_pid = spawn(__MODULE__, :connector, [ticker_pid, new_receiver_pid])
        :global.unregister_name(@name)
        :global.register_name(@name, connector_pid)
    end
  end

  # Listens for a new client to attach to ring.
  def connector(ticker_pid, current_listener_pid) do
    receive do
      { :connect, new_listener_pid, new_receiver_pid } ->
        send(ticker_pid, { :update, new_receiver_pid }) 
        send(new_listener_pid, { :connected, current_listener_pid })
    end
  end

  def ticker, do: ticker(self())
  def ticker(:initial), do: ticker(self(), :send)

  def ticker(next_pid) do
    receive do
      { :update, new_pid } ->
        ticker(new_pid)
      { :tick, sender } ->
        IO.puts "#{inspect sender} sent tick to #{inspect self()}"
        ticker(next_pid, :send)
    end
  end

  def ticker(next_pid, :send) do
    receive do
      { :update, new_pid } ->
        ticker(new_pid, :send)
      after @interval ->
        send(next_pid, { :tick, self() })
        ticker(next_pid)
    end
  end
end
You must be logged in to comment