9mm

9mm

How to quickly add to list in map?

Hello,

I have a collection like:

%{stats_a: [4, 5, 6], stats_b: [1, 2, 3]} # not actually integers (see next question)

Each of the 2 lists will be ~1 million items long.

I have a method which needs to push an item to beginning of each list. Ideally, it would in real-time chop the oldest/last items of the list so that it’s never longer than 1,000,000 items exactly

If theres no super fast way to do this however I will set a timer to do it every 1000ms… if there is an O(n) fast way to do it though I’ll do it in realtime. I need this timer anyway to aggregate stats so I’m not bothered if it needs to go there.

I will be pushing to it ~300-500 times/second.

Here’s what I had so far (without the limiting part yet…)

Is this crazy what I wrote?

  def handle_cast({:push_stats, key, stats}, state) do
    items = [stats | state[key]]
    new_state = state |> Map.put(key, items)
    {:noreply, new_state}
  end

Most Liked

peerreynders

peerreynders

defmodule Demo do
  def merge_values(_key, value1, value2),
    do: value1 + value2

  def reducer({key, data}, aggregate) do
    merge_data = fn all_data -> Map.merge(all_data, data, &merge_values/3) end
    Map.update(aggregate, key, data, merge_data)
  end

  def run(list),
    do: List.foldl(list, %{}, &reducer/2)
end

list = [
  {:group_1, %{timeout: 1, failure: 0, hits: 0}},
  {:group_1, %{timeout: 0, failure: 0, hits: 0}},
  {:group_2, %{timeout: 1, failure: 1, hits: 0}}
]

IO.inspect(Demo.run(list))
$ elixir demo.exs
%{
  group_1: %{failure: 0, hits: 0, timeout: 1},
  group_2: %{failure: 1, hits: 0, timeout: 1}
}
michalmuskala

michalmuskala

One thing to consider might be using an ets table instead of a server with state. In general, if you need high performance storage, ets tables are a very good solution. You could still have a process periodically reading from the table and doing some statistics.

peerreynders

peerreynders

There is Erlang’s queue data type.

peerreynders

peerreynders

Obviously I don’t care about line count - I like code that is easy to change:

defmodule Demo do
  defp initial_total(:response_time, time),
    do: if(is_number(time), do: {time, 1}, else: {0, 0})

  defp initial_total(_key, value),
    do: value || 0

  defp initial_aggregate({key, value}, aggregate),
    do: Map.put(aggregate, key, initial_total(key, value))

  defp merge_total(:response_time, {total_time, count} = total, time),
    do: if(is_number(time), do: {total_time + time, count + 1}, else: total)

  defp merge_total(_key, total, value),
    do: total + (value || 0)

  defp merge_aggregate({key, value}, aggregate) do
    new_total =
      case Map.fetch(aggregate, key) do
        {:ok, total} ->
          merge_total(key, total, value)

        _ ->
          initial_total(key, value)
      end

    Map.put(aggregate, key, new_total)
  end

  defp item_aggregator({key, data}, stats) do
    new_aggregate =
      case Map.fetch(stats, key) do
        {:ok, aggregate} ->
          Enum.reduce(data, aggregate, &merge_aggregate/2)

        _ ->
          Enum.reduce(data, %{}, &initial_aggregate/2)
      end

    Map.put(stats, key, new_aggregate)
  end

  defp finalize_total({:response_time, {total_time, count}}, aggregate) do
    if count > 0 do
      Map.put(aggregate, :response_time, div(total_time, count))
    else
      aggregate
    end
  end

  defp finalize_total({key, total}, aggregate) do
    Map.put(aggregate, key, total)
  end

  defp finalize_aggregate({key, aggregate}, stats),
    do: Map.put(stats, key, Enum.reduce(aggregate, %{}, &finalize_total/2))

  def make_stats(items) do
    items
    |> List.foldl(%{}, &item_aggregator/2)
    |> Enum.reduce(%{}, &finalize_aggregate/2)
  end
end

#
# item: {:group_1, data}
# data: %{timeout: 0, failure: 0, hits: 1, response_time: 100}
# value: associated with a "key" inside the "data" Map
# aggregate: %{failure: 0, hits: 2, response_time: {1100,2}, timeout: 0}
# total: value associated with a "key" inside the "aggregate" Map
# finalized_aggregate: %{failure: 0, hits: 2, response_time: 550, timeout: 0}
# stats: %{feed_key => (finalized_)aggregate}
#

feed_items = [
  {:group_1, %{timeout: 0, failure: 0, hits: 1, response_time: 100}},
  {:group_1, %{timeout: 0, failure: 0, hits: 1, response_time: 1000}},
  {:group_2, %{timeout: 0, failure: 0, hits: 1, response_time: 50}},
  {:group_2, %{timeout: 0, failure: 0, hits: 1, response_time: 2000}},
  {:group_3, %{timeout: 0, failure: 1, hits: 0}},
  {:group_3, %{timeout: 1, failure: 1, hits: 0, response_time: nil}}
]

IO.inspect(Demo.make_stats(feed_items))
$ elixir demo.exs
%{
  group_1: %{failure: 0, hits: 2, response_time: 550, timeout: 0},
  group_2: %{failure: 0, hits: 2, response_time: 1025, timeout: 0},
  group_3: %{failure: 2, hits: 0, timeout: 1}
}
9mm

9mm

Damn that is intense, I need to reread this a few times… thank you. One thing about functional programming is this stuff gets a lot less intuitive/easy. hopefully it gets easeir in time

Where Next?

Popular in Questions Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
_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
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
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
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
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement