Chunking list based on struct type changing

♀尐吖头ヾ 提交于 2019-12-10 03:22:43

问题


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?


回答1:


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: []



回答2:


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



回答3:


Enum.chunk_by/2 currently does not provide access to the previous element so we can not use Enum.chunk_by/2 in this case. We will have to fallback to reduce/3

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

Below is one way to go about producing the output you want, given the values [ %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

Notice that I always prepend an element to a list. This is because appending to a list in Elixir is an expensive operation.

Hope this solution helps!



来源:https://stackoverflow.com/questions/30280966/chunking-list-based-on-struct-type-changing

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!