基于结构类型更改的分块列表

时间:2015-05-16 21:29:25

标签: elixir

我有一个列表,我希望根据从结构类型B到A的转换进行分块。例如,我有以下内容:

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

我希望将这些数据分成2个元素的列表,其中包含:

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

如果输入最初是所有A或所有B,则输出将保持不变,因为没有发生B-> A转换。

我想Enum.chunk_by/2是可行的方法,但我无法弄清楚如何维护前一个元素的上下文以了解何时分割。

像这样的惯用解决方案是什么样的?

3 个答案:

答案 0 :(得分:5)

另一种方法是使用纯递归:

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

答案 1 :(得分:4)

Enum.chunk_by/2目前不提供对上一个元素的访问权限,因此在这种情况下我们无法使用Enum.chunk_by/2。我们将不得不回到reduce/3

在所有Enum个函数中,reduce/3是最灵活的,并且在Enum函数的大多数(如果不是全部)内部使用。

在给定值[ %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

请注意,我总是将一个元素添加到列表中。这是因为附加到Elixir中的列表是一项昂贵的操作。

希望此解决方案有所帮助!

答案 2 :(得分:4)

另一个替代方法是使用chunk_by结构类型然后执行另一个合并列表的传递(除非列表包含%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: []