List enumeration based on structure type change

I have a list that I want to split based on the transition from structure 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 this data to be broken down into a 2-element list containing:

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

If at first the input should have been all A or all B, the output would not have changed, since there was no transition B-> A.

I believe Enum.chunk_by/2 is the way to go, but it’s hard for me to figure out how to maintain the context of the previous element in order to know when to split.

What does this idiomatic solution look like?

+8
elixir
source share
3 answers

Another alternative is chunk_by by structure type, then perform another pass joining the lists (unless 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: [] 
+4
source share

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 
+5
source share

Enum.chunk_by/2 does not currently provide access to the previous item, so we cannot use Enum.chunk_by/2 in this case. We will have to step back to reduce/3

Of all the functions, Enum reduce/3 is the most flexible and is used within most, if not all, Enum functions.

Below is one way to make the conclusion you want, taking into account the values ​​of [ %A{}, %A{}, %B{}, %B{}, %B{}, %A{}, %A{}, %B{} ] :

  values |> Enum.reduce([[]], fn (elem, acc) -> prev_list = List.first(acc) prev_elem = List.first(prev_list) b_changed_to_a? = fn -> prev_elem.__struct__ == B && elem.__struct__ == A end if is_nil(prev_elem) || !b_changed_to_a?.() do List.replace_at(acc, 0, [elem|prev_list]) else [[elem]|acc] end end) |> Enum.map(&Enum.reverse/1) |> Enum.reverse 

Please note that I always add an item to the list. This is because adding to the list in Elixir is an expensive operation.

We hope this solution helps!

+4
source share

All Articles