Subtraction between 2 List of maps

Hi,

I am trying to do a subtraction between 2 list of maps.

Before:-

iex(9)> buy                           
[
  %{quantity: 40, code: 1001},
  %{quantity: 18, code: 933},
  %{quantity: 4445, code: 9394},
  %{quantity: 1305, code: 915},
  %{quantity: 43, code: 8394}
]

iex(10)> sell                          
[
  %{quantity: 3400, code: 915},
  %{quantity: 22, code: 9394},
  %{quantity: 24, code: 10003},
  %{quantity: 22, code: 10013},
  %{quantity: 38, code: 10.5}
]

the expected output is After subtraction:-

 buy 
 ----  
[
  %{quantity: 40, code: 1001},
  %{quantity: 18, code: 933},
  %{quantity: 2245, code: 9394},
  %{quantity: 43, code: 8394}
]
sell
----
[
  %{quantity: 2095, code: 915},
  %{quantity: 24, code: 10003},
  %{quantity: 22, code: 10013},
  %{quantity: 38, code: 1015}
]

The buy list removes the %{quantity: 1305, code: 915}, after the quantity is subtracted by the %{quantity: 3400, code: 915}, from sell leaving a balance of %{quantity: 2095, code: 915} in sell.

And, the sell list removes %{quantity: 22, code: 9394} after it has been subtracted by %{quantity: 4445, code: 9394} from buy leaving a balance of %{quantity: 2245, code: 9394}, in buy

Any advice how to solve this?

What have you tried so far?

1 Like

how come 4445 - 22 is 2245

1 Like

Sorry, that was a typo error it should be %{quantity: 2200 code: 9394} from sell

I’ll provide a tip: Turn each list into a map where the key is the code and the value is the quantity. Then you can easily update the quantities for each code. You can turn it back into a list at the end if you want. Give it a shot, and we’ll be happy to offer feedback.

2 Likes

Exactly. Sounds like a list is not the data structure that you need, but a map.

I have something like this:

 def check(input) do
    input
    |> Enum.reduce(%{}, fn %{"code" => code, "quantity" => quantity}, map ->
                   Map.update(map, code, quantity, &(&1 + quantity)) end)
 end

able to get something like this:

iex(18)> buy |> check
%{8939 => 43, 915 => 1305, 9394 => 4445, 933 => 18, 1001 => 40}

iex(19)> sell |> check
%{915 => 3400, 9394 => 2200, 10003 => 24, 10013 => 22, 1015 => 38}

  • qty = 10 i.e. buy 10 units
  • qty = -10 i.e. sell 10 units

You could do something like this…

iex> transform = fn list -> 
...> Enum.reduce(list, %{}, fn %{code: c, quantity: q}, acc -> Map.put(acc, c, q) end) 
...> end                             
#Function<6.128620087/1 in :erl_eval.expr/5>
iex> transform.(sell) |> 
...> Enum.reduce(transform.(buy), fn {k, v}, acc -> Map.update(acc, k, -v, &(&1 - v)) end) |> 
...> Enum.map(fn {k, v} -> %{code: k, quantity: v} end)
[
  %{code: 915, quantity: -2095},
  %{code: 933, quantity: 18},
  %{code: 1001, quantity: 40},
  %{code: 8394, quantity: 43},
  %{code: 9394, quantity: 4423},
  %{code: 10003, quantity: -24},
  %{code: 10013, quantity: -22},
  %{code: 10.5, quantity: -38}
]

# or

iex> transform.(buy) |> 
...> Enum.reduce(transform.(sell), fn {k, v}, acc -> Map.update(acc, k, v, &(v - &1)) end) |> 
...> Enum.map(fn {k, v} -> %{code: k, quantity: v} end)

It returns the balance… You could get the new buy and sell by checking if quantity >= 0

It does not work if any of the list as duplicate codes! For this to work… You would have to change transform.

just to double confirm, when the quantity is negative, it refers to the sell, when it is positive it is pointing to buy correct?

Yes, but as mentionned by previous posters

  1. A map is a better structure for solving this problem (with id/code acting as key)
  2. A sell is better expressed as a negative quantity, so math is easy… it’s buy + sell
  3. Some sample of code You tried is appreciated

Thanks @kokolegorille

Its working. I’ve used your example…

Doing the calculation and formatting the floating point

def diff_calculation(buy,sell) do
    transform = fn list ->  Enum.reduce(list, %{}, fn %{code: code, quantity: quantity}, map -> Map.put(map, code, quantity) end)  end
    transform.(sell)
    |> Enum.reduce(transform.(buy), fn {k, v}, acc -> Map.update(acc, k, -v, &(&1 - v)) end)
    |> Enum.map(fn {k, v} -> %{code: k, quantity: Float.round(v,3)} end)
  end

Sorting the buy

   def organize_buy() do
    diff_calculation(buy(),sell())
    |> Enum.filter(&(&1[:quantity]) > 0)
    |> Enum.sort_by(&Map.fetch(&1, "code"))
    |> Enum.reverse()
  end

Sorting the Sell and changing the quantity to positive

  def organize_sell() do
    negative = diff_calculation(buy(),sell()) |> Enum.filter(&(&1[:quantity]) < 0)
    positive = for i <- negative , do: i |> Map.update!(:quantity, &(&1 * -1))
    positive
    |> Enum.sort_by(&Map.fetch(&1, "code"))
  end

Hey, here is my complete version which even supports merging of data in case you expect to have duplicated codes:

defmodule Example do
  import :erlang, only: [map_get: 2]

  # those 2 lines as well as true match in next clause are optional
  # this by default merges buys and sells by code by summing them
  # if you have your own code or you are using for example some SQL functions
  # then feel free to remove those parts
  def sample(buy, sell, merged \\ false)
  def sample(buy, sell, false), do: buy |> merge() |> sample(merge(sell), true)

  # we are going to get index of each buy in order to easy access it using
  # List.delete_at/2 and List.update_at/3
  # we need to reverse it, because we can't delete for example 2nd item before 4th item
  def sample(buy, sell, true),
    do: buy |> Enum.with_index() |> Enum.reverse() |> Enum.reduce({buy, sell}, &do_sample/2)

  # next 3 private functions are optional
  # they are needed only if you want to keep merge code
  # feel free to remove it if you don't need such functionality
  defp merge(list), do: list |> Enum.reduce(%{}, &do_merge/2) |> Enum.map(&to_item/1)

  defp do_merge(%{code: key, quantity: value}, map),
    do: Map.update(map, key, value, &(&1 + value))

  defp to_item({key, value}), do: %{code: key, quantity: value}

  defp do_sample({item, index}, {buy, sell}) do
    {subtracted, updated_sell} = update_sell(item, sell)
    updated_buy = update_buy(buy, index, item.quantity, subtracted)
    {updated_buy, updated_sell}
  end

  defp update_sell(item, [head | tail]) when map_get(:code, item) == map_get(:code, head) do
    if head.quantity > item.quantity do
      {item.quantity, [%{head | quantity: head.quantity - item.quantity} | tail]}
    else
      {head.quantity, tail}
    end
  end

  defp update_sell(_item, []), do: {0, []}

  defp update_sell(item, [head | tail]) do
    {subtracted, rest} = update_sell(item, tail)
    {subtracted, [head | rest]}
  end

  defp update_buy(list, _index, _quantity, subtracted) when subtracted == 0, do: list

  # this clause requires above reverse call
  # if you comment this you can remove such call,
  # but you will receive list with quantity set to 0 items
  defp update_buy(list, index, quantity, subtracted) when subtracted == quantity,
    do: List.delete_at(list, index)

  defp update_buy(list, index, _quantity, subtracted),
    do: List.update_at(list, index, &%{&1 | quantity: &1.quantity - subtracted})
end

expected_buy =
  Enum.sort_by(
    [
      %{quantity: 40, code: 1001},
      %{quantity: 18, code: 933},
      %{quantity: 2245, code: 9394},
      %{quantity: 43, code: 8394}
    ],
    & &1.code
  )

expected_sell =
  Enum.sort_by(
    [
      %{quantity: 2095, code: 915},
      %{quantity: 24, code: 10003},
      %{quantity: 22, code: 10013},
      %{quantity: 38, code: 1015}
    ],
    & &1.code
  )

original_buy = [
  %{quantity: 40, code: 1001},
  %{quantity: 18, code: 933},
  %{quantity: 4445, code: 9394},
  %{quantity: 1305, code: 915},
  %{quantity: 43, code: 8394}
]

original_sell = [
  %{quantity: 3400, code: 915},
  %{quantity: 2200, code: 9394},
  %{quantity: 24, code: 10003},
  %{quantity: 22, code: 10013},
  %{quantity: 38, code: 1015}
]

{new_buy, new_sell} = Example.sample(original_buy, original_sell)
IO.inspect(new_buy, label: "Calculated buy")
IO.inspect(new_sell, label: "Calculated sell")
IO.inspect(new_buy == expected_buy and new_sell == expected_sell, label: "All good")

Let me know what do you think about it.

Given that the cat is now out of the bag - the variation I had:

defmodule Demo do

  defp buy_to_tuple(%{code: code, quantity: qty}),
    do: {code, qty}

  defp sell_to_tuple(%{code: code, quantity: qty}),
    do: {code, -qty}

  defp update_qty({code, qty}, map),
    do: Map.update(map, code, qty, &(&1 + qty))

  defp split_buy_sell({code, qty} = item, {buys, sells} = acc) do
    cond do
      qty > 0 ->
        {[item | buys], sells}
      qty < 0 ->
        {buys, [{code, -qty} | sells]}
      true ->
        # ignore 0 qty
        acc
    end
  end

  defp tuple_to_map({code,qty}),
    do: Map.new([code: code, quantity: qty])

  defp to_maps({buys, sells}),
    do: {Enum.map(buys, &tuple_to_map/1), Enum.map(sells, &tuple_to_map/1)}

  def run(buy, sell) do
    buys = Map.new(buy, &buy_to_tuple/1)

      sell
      |> Enum.map(&sell_to_tuple/1)
      |> List.foldl(buys, &update_qty/2)
      |> Enum.reduce({[],[]}, &split_buy_sell/2)
      |> to_maps()
  end
end

sell = [
  %{quantity: 3400, code: 915},
  %{quantity: 22, code: 9394},
  %{quantity: 24, code: 10003},
  %{quantity: 22, code: 10013},
  %{quantity: 999, code: 999},
  %{quantity: 38, code: 1015}
]

buy = [
  %{quantity: 40, code: 1001},
  %{quantity: 18, code: 933},
  %{quantity: 4445, code: 9394},
  %{quantity: 1305, code: 915},
  %{quantity: 999, code: 999},
  %{quantity: 43, code: 8394}
]


{buy, sell} = Demo.run(buy,sell)

IO.puts("#{inspect buy}")
IO.puts("#{inspect sell}")

Alternately:

# $ elixir demo.exs
# [%{code: 9394, quantity: 4423}, %{code: 8394, quantity: 43}, %{code: 1001, quantity: 40}, %{code: 933, quantity: 18}]
# [%{code: 10013, quantity: 22}, %{code: 10003, quantity: 24}, %{code: 1015, quantity: 38}, %{code: 915, quantity: 2095}]
#
defmodule Demo do
  defp buy_to_tuple(%{code: code, quantity: qty}),
    do: {code, qty}

  defp sell_to_tuple(%{code: code, quantity: qty}),
    do: {code, -qty}

  defp update_qty({code, qty}, map),
    do: Map.update(map, code, qty, &(&1 + qty))

  defp into_map(buy_or_sell_list, map, convert) do
    buy_or_sell_list
    |> Enum.map(convert)
    |> List.foldl(map, &update_qty/2)
  end

  defp split_buy_sell({code, qty} = item, {buys, sells} = acc) do
    cond do
      qty > 0 ->
        {[item | buys], sells}

      qty < 0 ->
        {buys, [{code, -qty} | sells]}

      true ->
        # ignore 0 qty
        acc
    end
  end

  defp tuple_to_map({code, qty}),
    do: Map.new(code: code, quantity: qty)

  defp to_maps({buys, sells}),
    do: {Enum.map(buys, &tuple_to_map/1), Enum.map(sells, &tuple_to_map/1)}

  def run(buy_list, sell_list) do
    buys = into_map(buy_list, %{}, &buy_to_tuple/1)

    sell_list
    |> into_map(buys, &sell_to_tuple/1)
    |> Enum.reduce({[], []}, &split_buy_sell/2)
    |> to_maps()
  end
end

sell = [
  %{quantity: 2000, code: 915},
  %{quantity: 1400, code: 915},
  %{quantity: 22, code: 9394},
  %{quantity: 24, code: 10003},
  %{quantity: 22, code: 10013},
  %{quantity: 999, code: 999},
  %{quantity: 38, code: 1015}
]

buy = [
  %{quantity: 40, code: 1001},
  %{quantity: 18, code: 933},
  %{quantity: 3000, code: 9394},
  %{quantity: 1445, code: 9394},
  %{quantity: 1305, code: 915},
  %{quantity: 999, code: 999},
  %{quantity: 43, code: 8394}
]

{buy, sell} = Demo.run(buy, sell)

IO.puts("#{inspect(buy)}")
IO.puts("#{inspect(sell)}")
1 Like