Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chunking list based on struct type changing

Tags:

elixir

I have a list I want to chunk up based on a transition from struct type B to A. So for example, I have the following:

iex(1)> defmodule A, do: defstruct []
{:module, A ...
iex(2)> defmodule B, do: defstruct []
{:module, B ...
iex(3)> values = [ %A{}, %A{}, %B{}, %B{}, %B{}, %A{}, %A{}, %B{} ]
[%A{}, %A{}, %B{}, %B{}, %B{}, %A{}, %A{}, %B{}]

I want to have that data chunked up into a 2-element list containing:

[ [ %A{}, %A{}, %B{}, %B{}, %B{} ], [ %A{}, %A{}, %B{} ] ]

If the input were to be all A's or all B's initially, the output would be unchanged, since no B->A transition occurred.

I imagine Enum.chunk_by/2 is the way to go, but I'm having trouble figuring out how to maintain the context of the previous element to know when to split.

What does an idiomatic solution to something like this look like?

like image 475
Nick Veys Avatar asked May 16 '15 21:05

Nick Veys


2 Answers

Yet another approach is to use pure recursion:

def collect_chunks([]), do: []
def collect_chunks(list) do
  {chunk, post_chunk} = collect_chunk(list)
  [chunk | collect_chunks(post_chunk)]
end

defp collect_chunk([]), do: {[], []}
defp collect_chunk([%B{} = last_element | [%A{} | _] = post_chunk]), do: {[last_element], post_chunk}
defp collect_chunk([el | rest]) do
  {remaining_chunk, post_chunk} = collect_chunk(rest)
  {[el | remaining_chunk], post_chunk}
end
like image 97
sasajuric Avatar answered Sep 27 '22 19:09

sasajuric


Another alternative is to chunk_by the struct type then do another pass merging the lists (except when the list contains %B{}):

def chunk(structs) do
  structs
  |> Enum.chunk_by(& &1.__struct__)
  |> merge()
end

# Don't merge when current is %B
defp merge([[%B{}|_]=h|t]), do: [h|merge(t)]

# Merge all others
defp merge([curr, next|t]), do: [curr ++ next|merge(t)]

# We are done
defp merge([]), do: []
like image 31
José Valim Avatar answered Sep 27 '22 20:09

José Valim