¿Cómo puedo repetir (recursivamente o con reduce) un List de Map s mientras lo comparo con el último elemento de la lista?

Por ejemplo, digamos que tengo una lista de mapas como esta:

    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)}]

Quiero modificar el atributo cluster de cada mapa para representar el grupo en el que se encuentra con respecto al orden en el que se encuentra.

El uso de group_by es excelente si los identificadores de cluster no se repiten.

Quiero agruparlos a medida que cambian de grupo, lo que da como resultado algo como esto:

            [%{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)}]

Para hacer esto, necesito comparar el elemento actual en la lista con el anterior. Había comenzado con algo como esto (a continuación) y me detuve porque sé que no hará referencia al elemento anterior para comparar el elemento actual con el anterior:

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
1
DogEatDog 18 feb. 2018 a las 18:17

2 respuestas

La mejor respuesta

Deberá mantener dos números enteros en el acumulador: el grupo resultante actual (que se incrementará en 1 en cada cambio) y el último valor sin procesar del grupo.

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

Salida:

[
  %{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>}
]
1
Dogbert 18 feb. 2018 a las 17:23

El enfoque más elixirista sería usar la coincidencia de patrones en las cláusulas de función dentro de 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)
0
Aleksei Matiushkin 19 feb. 2018 a las 07:03