Your example is giving the wrong values though, ZG would be the worse to buy since the price is highest there?
Because you need them all, ordered and not only the lowest and highest I would imagine a custom reduce would be the best option for doing it on almost a single pass.
I’ve added also other entries with the same prices to the sample list.
vals = [
%{id: BTC, name: "Binance", price: 100},
%{id: BTC, name: "VCC Exchange", price: 102},
%{id: BTC, name: "CoinBene", price: 104},
%{id: BTC, name: "CoinMale", price: 104},
%{id: BTC, name: "BitZ", price: 140},
%{id: BTC, name: "Huobi Global", price: 150},
%{id: BTC, name: "Huobi Local", price: 150},
%{id: BTC, name: "ZG.com", price: 170},
]
And a module for doing this:
defmodule Kombucha do
def create(list_of_vals) do
{mins, maxs, maps} = sort(list_of_vals)
build_output(mins, maxs, maps)
end
def sort(list_of_vals) do
list_of_vals
|> Enum.reduce({[], [], %{}}, fn(%{name: name, price: price}, {mins, maxs, maps}) ->
new_mins = add_min(price, mins)
new_maxs = add_max(price, maxs)
new_maps = Map.update(maps, price, [name], fn(existing) -> [name | existing] end)
{new_mins, new_maxs, new_maps}
end)
end
def add_max(price, rem, bigger \\ [])
def add_max(price, [], bigger), do: :lists.flatten([bigger, price])
def add_max(price, [h | t] = all, bigger) when price > h, do: :lists.flatten([bigger, price | all])
def add_max(price, [price | t] = all, bigger), do: :lists.flatten([bigger | all])
def add_max(price, [h | t], bigger), do: add_max(price, t, bigger ++ [h])
def add_min(price, rem, smaller \\ [])
def add_min(price, [], smaller), do: :lists.flatten([smaller, price])
def add_min(price, [h | t] = all, smaller) when price < h, do: :lists.flatten([smaller, price | all])
def add_min(price, [price | t] = all, smaller), do: :lists.flatten([smaller | all])
def add_min(price, [h | t], smaller), do: add_min(price, t, smaller ++ [h])
def build_output(mins, maxs, maps) do
Enum.reduce(mins, [], fn(min, acc_1) ->
Enum.reduce(maxs, acc_1, fn(max, acc_2) ->
case min < max do
true ->
min_names = Map.get(maps, min)
max_names = Map.get(maps, max)
Enum.reduce(min_names, acc_2, fn(min_name, acc_3) ->
Enum.reduce(max_names, acc_3, fn(max_name, acc_4) ->
[%{buy: min_name, sell: max_name, profit: (max - min)} | acc_4]
end)
end)
false -> acc_2
end
end)
end)
|> Enum.sort(fn(%{profit: profit_a}, %{profit: profit_b}) -> profit_a >= profit_b end)
end
end
This should give you some ideas for how to tackle it. The build_out
reductions could probably use reduce_while
on the two outer reduces but it would clog up the logic and it doesn’t seem like it would be a significant improvement. You could also change it to keep adding entries to the output even if the profit was 0 or negative.
iex> Kombucha.create(vals)
[
%{buy: "Binance", profit: 70, sell: "ZG.com"},
%{buy: "VCC Exchange", profit: 68, sell: "ZG.com"},
%{buy: "CoinBene", profit: 66, sell: "ZG.com"},
%{buy: "CoinMale", profit: 66, sell: "ZG.com"},
%{buy: "Binance", profit: 50, sell: "Huobi Global"},
%{buy: "Binance", profit: 50, sell: "Huobi Local"},
%{buy: "VCC Exchange", profit: 48, sell: "Huobi Global"},
%{buy: "VCC Exchange", profit: 48, sell: "Huobi Local"},
%{buy: "CoinBene", profit: 46, sell: "Huobi Global"},
%{buy: "CoinBene", profit: 46, sell: "Huobi Local"},
%{buy: "CoinMale", profit: 46, sell: "Huobi Global"},
%{buy: "CoinMale", profit: 46, sell: "Huobi Local"},
%{buy: "Binance", profit: 40, sell: "BitZ"},
%{buy: "VCC Exchange", profit: 38, sell: "BitZ"},
%{buy: "CoinBene", profit: 36, sell: "BitZ"},
%{buy: "CoinMale", profit: 36, sell: "BitZ"},
%{buy: "BitZ", profit: 30, sell: "ZG.com"},
%{buy: "Huobi Global", profit: 20, sell: "ZG.com"},
%{buy: "Huobi Local", profit: 20, sell: "ZG.com"},
%{buy: "BitZ", profit: 10, sell: "Huobi Global"},
%{buy: "BitZ", profit: 10, sell: "Huobi Local"},
%{buy: "Binance", profit: 4, sell: "CoinBene"},
%{buy: "Binance", profit: 4, sell: "CoinMale"},
%{buy: "VCC Exchange", profit: 2, sell: "CoinBene"},
%{buy: "VCC Exchange", profit: 2, sell: "CoinMale"},
%{buy: "Binance", profit: 2, sell: "VCC Exchange"}
]