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?
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
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: []
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With