0

I have this map

%{
  total: 38,
  value: 22
}

And would like to add the key :ratio. Is there a way to write immediately:

%{
  total: 38,
  value: 22,
  ratio: __SELF__.value / __SELF__.total
}

or do I need to create another map to achieve this?

Thanks

Augustin Riedinger
  • 20,909
  • 29
  • 133
  • 206

2 Answers2

3

All data is immutable, so you always have to make a new map.

A simple way, assuming your map is called map:

iex> Map.put(map, :ratio, map.value / map.total)
%{ratio: 0.5789473684210527, total: 38, value: 22}

If you mean that you want to create the map before it already exists, then it would be better to put total and value into variables, and use them to build the map:

defmodule Example do
  def make_map(total, value) do
    %{total: total, value: value, ratio: value / total}
  end
end

iex(1)> Example.make_map(38, 22)
%{ratio: 0.5789473684210527, total: 38, value: 22}
Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
  • Makes sense. Since I really wanted to avoid assigning the temporary map to a variable nor build a function for this, I'll go for `%{value: 12, total: 18} |> (&(Map.put(&1, :ratio, &1.value / &1.total))).()`. Not very elegant I reckon though – Augustin Riedinger Jul 03 '19 at 16:01
  • 2
    Repeating the numbers would probably be simpler: `%{value: 12, total: 18, ratio: 12/18}` – Adam Millerchip Jul 03 '19 at 16:15
  • Except of course the numbers are not static but come from an expensive SQL query. – Augustin Riedinger Jul 04 '19 at 09:44
  • 1
    Then your function works fine, but obviously I prefer mine. :) You can inline it like this: `map |> (fn %{value: v, total: t} -> %{value: v, total: t, ratio: v/t} end).()` – Adam Millerchip Jul 04 '19 at 12:32
  • Those numbers coming from the expensive SQL query aren't stored in variables in memory in any way? You've clearly got access to both total and value somehow... it doesn't make sense why you want to put them a self-referential structure and then retrieve them from the structure instead of just using them directly. Adam's answer is right. – Mark Wilbur Jul 04 '19 at 18:45
  • You don't _HAVE_ to make a new map var, but I think it's definitely ideal to do so in the spirit of functional programming. – Ian Jul 07 '19 at 22:42
-2

I really wanted to avoid assigning the temporary map to a variable.

Well, because the data at a specific memory location is immutable, elixir can safely use pointers to those memory locations inside new collections. Therefore, creating a temporary variable with intermediate results does not double the amount of memory used. For instance, if you begin with a map that has 1 million entries and you create a new map with one additional entry, you do not use total memory of:

  old_map       new_map
    |              |
    V              V
1 million + ( 1 million + 1) 

Rather you only use additional memory of:

   new_entry
      |
      V
      1 + pointer_to_old_map

...plus a little extra for the new map's bookkeeping. Like this:

             old_map
               ^
               |
 new_map = %{  +, a: 10}

Is there a way to write immediately:

%{
  total: 38,
  value: 22,
  ratio: __SELF__.value / __SELF__.total
}

Yes:

%{
  total: 38,
  value: 22,
  ratio: 22/38
}

Now, if you have a list of maps to which you want to add a ratio key:

data = [
  %{total: 38, value: 22},
  %{total: 40, value: 22},
  %{total: 44, value: 22}
]

for %{total: t, value: v}=map <- data do
  Map.put(map, :ratio, v/t)
end

output:

[
  %{ratio: 0.5789473684210527, total: 38, value: 22},
  %{ratio: 0.55, total: 40, value: 22},
  %{ratio: 0.5, total: 44, value: 22}
]

After each iteration of the for comprehension, the memory locations of t and v are immediately subject to garbage collection and a pointer to the memory location of map is used in the new map.

7stud
  • 46,922
  • 14
  • 101
  • 127
  • https://stackoverflow.com/questions/56871601/elixir-make-a-self-reference-in-a-map/56875821#comment100332535_56872312 – Mark Wilbur Jul 06 '19 at 05:58
  • @MarkWilbur, https://stackoverflow.com/questions/37912558/one-labels-constraints-added-using-ib-one-labels-constraints-added-programmat – 7stud Jul 08 '19 at 17:24