Reputation: 3037
How can I loop (recursively or with reduce
) a List
of Map
s while comparing to the last element in the list?
For example, let's say I have a List of maps like this:
datetime = Timex.beginning_of_day(Timex.now)
data = [
%{a: 0, cluster: 0, time: datetime},
%{a: 1, cluster: 0, time: Timex.shift(datetime, minutes: 3)},
%{a: 2, cluster: 0, time: Timex.shift(datetime, minutes: 6)},
%{a: 3, cluster: 0, time: Timex.shift(datetime, minutes: 9)},
%{a: 4, cluster: 1, time: Timex.shift(datetime, minutes: 12)},
%{a: 5, cluster: 1, time: Timex.shift(datetime, minutes: 15)},
%{a: 6, cluster: 0, time: Timex.shift(datetime, minutes: 18)},
%{a: 7, cluster: 0, time: Timex.shift(datetime, minutes: 21)},
%{a: 8, cluster: 0, time: Timex.shift(datetime, minutes: 23)},
%{a: 9, cluster: 2, time: Timex.shift(datetime, minutes: 26)},
%{a: 10, cluster: 2, time: Timex.shift(datetime, minutes: 29)},
%{a: 11, cluster: 2, time: Timex.shift(datetime, minutes: 32)},
%{a: 12, cluster: 1, time: Timex.shift(datetime, minutes: 35)},
%{a: 13, cluster: 1, time: Timex.shift(datetime, minutes: 38)},
]
I want to modify the cluster
attribute of each map to represent the group that it is in with respect to the order that it is in.
Using the group_by
is great if the cluster
ids were not repeating.
I want to group them as they change group, resulting in something like this:
[
%{a: 0, cluster: 0, time: datetime},
%{a: 1, cluster: 0, time: Timex.shift(datetime, minutes: 3)},
%{a: 2, cluster: 0, time: Timex.shift(datetime, minutes: 6)},
%{a: 3, cluster: 0, time: Timex.shift(datetime, minutes: 9)},
%{a: 4, cluster: 1, time: Timex.shift(datetime, minutes: 12)},
%{a: 5, cluster: 1, time: Timex.shift(datetime, minutes: 15)},
%{a: 6, cluster: 2, time: Timex.shift(datetime, minutes: 18)},
%{a: 7, cluster: 2, time: Timex.shift(datetime, minutes: 21)},
%{a: 8, cluster: 2, time: Timex.shift(datetime, minutes: 23)},
%{a: 9, cluster: 3, time: Timex.shift(datetime, minutes: 26)},
%{a: 10, cluster: 3, time: Timex.shift(datetime, minutes: 29)},
%{a: 11, cluster: 3, time: Timex.shift(datetime, minutes: 32)},
%{a: 12, cluster: 4, time: Timex.shift(datetime, minutes: 35)},
%{a: 13, cluster: 4, time: Timex.shift(datetime, minutes: 38)},
]
To do this, I need to compare the current item in the list to the previous one. I had started with something like this (below) and stopped because I know it will not reference the previous item to compare the current item to the previous one:
Enum.map_reduce(data, 0, fn(x, acc) -> cluster_grouping(x, acc) end)
def cluster_grouping(x, acc) do
cond do
x.cluster == acc -> {Map.put(x, :cluster, acc), acc}
x.cluster > acc -> {Map.put(x, :cluster, acc), acc + 1}
end
end
Upvotes: 3
Views: 936
Reputation: 222040
You'll need to keep two integers in the accumulator: the current resulting cluster (which will increment by 1 on every change) and the last raw value of cluster.
datetime = Timex.beginning_of_day(Timex.now)
data = [
%{a: 0, cluster: 0, time: datetime},
%{a: 1, cluster: 0, time: Timex.shift(datetime, minutes: 3)},
%{a: 2, cluster: 0, time: Timex.shift(datetime, minutes: 6)},
%{a: 3, cluster: 0, time: Timex.shift(datetime, minutes: 9)},
%{a: 4, cluster: 1, time: Timex.shift(datetime, minutes: 12)},
%{a: 5, cluster: 1, time: Timex.shift(datetime, minutes: 15)},
%{a: 6, cluster: 0, time: Timex.shift(datetime, minutes: 18)},
%{a: 7, cluster: 0, time: Timex.shift(datetime, minutes: 21)},
%{a: 8, cluster: 0, time: Timex.shift(datetime, minutes: 23)},
%{a: 9, cluster: 2, time: Timex.shift(datetime, minutes: 26)},
%{a: 10, cluster: 2, time: Timex.shift(datetime, minutes: 29)},
%{a: 11, cluster: 2, time: Timex.shift(datetime, minutes: 32)},
%{a: 12, cluster: 1, time: Timex.shift(datetime, minutes: 35)},
%{a: 13, cluster: 1, time: Timex.shift(datetime, minutes: 38)},
]
Enum.map_reduce(data, {0, 0}, fn x, {i, last} ->
i = if x.cluster == last, do: i, else: i + 1
{Map.put(x, :cluster, i), {i, x.cluster}}
end)
|> elem(0)
|> IO.inspect
Output:
[
%{a: 0, cluster: 0, time: #DateTime<2018-02-18 00:00:00Z>},
%{a: 1, cluster: 0, time: #DateTime<2018-02-18 00:03:00Z>},
%{a: 2, cluster: 0, time: #DateTime<2018-02-18 00:06:00Z>},
%{a: 3, cluster: 0, time: #DateTime<2018-02-18 00:09:00Z>},
%{a: 4, cluster: 1, time: #DateTime<2018-02-18 00:12:00Z>},
%{a: 5, cluster: 1, time: #DateTime<2018-02-18 00:15:00Z>},
%{a: 6, cluster: 2, time: #DateTime<2018-02-18 00:18:00Z>},
%{a: 7, cluster: 2, time: #DateTime<2018-02-18 00:21:00Z>},
%{a: 8, cluster: 2, time: #DateTime<2018-02-18 00:23:00Z>},
%{a: 9, cluster: 3, time: #DateTime<2018-02-18 00:26:00Z>},
%{a: 10, cluster: 3, time: #DateTime<2018-02-18 00:29:00Z>},
%{a: 11, cluster: 3, time: #DateTime<2018-02-18 00:32:00Z>},
%{a: 12, cluster: 4, time: #DateTime<2018-02-18 00:35:00Z>},
%{a: 13, cluster: 4, time: #DateTime<2018-02-18 00:38:00Z>}
]
Upvotes: 3
Reputation: 120990
The more elixirish approach would be to use pattern matching on function clauses inside map-reducer:
Enum.map_reduce(data, {0, 0}, fn
%{cluster: last} = x, {i, last} ->
{%{x | cluster: i}, {i, last}}
%{cluster: last} = x, {i, _} ->
{%{x | cluster: i + 1}, {i + 1, last}}
end)
Upvotes: 0