基于结构类型更改的分块列表
Chunking list based on struct type changing
我有一个列表,我想根据从结构类型 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
是可行的方法,但我无法弄清楚如何维护前一个元素的上下文以知道何时拆分。
像这样的事情的惯用解决方案是什么样的?
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 中附加到列表是一项昂贵的操作。
希望此解决方案对您有所帮助!
另一种选择是 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: []
另一种方法是使用纯递归:
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
我有一个列表,我想根据从结构类型 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
是可行的方法,但我无法弄清楚如何维护前一个元素的上下文以知道何时拆分。
像这样的事情的惯用解决方案是什么样的?
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 中附加到列表是一项昂贵的操作。
希望此解决方案对您有所帮助!
另一种选择是 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: []
另一种方法是使用纯递归:
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