Stream 使用自定义分块函数对流进行分块
我有一个包含两个元素元组的枚举的流,例如:Stream 使用自定义分块函数对流进行分块,stream,elixir,Stream,Elixir,我有一个包含两个元素元组的枚举的流,例如: [ {:dialogue, %{}}, {:info, %{}}, {:info, %{}}, {:info, %{}}, {:dialogue, %{}}, {:dialogue, %{}}, {:info, %{}} ... ] 我的最终目标是将对象分块,这样每个分块都以一个{:dialogue,%{}}元组开始 最初我的代码如下: stream |> Stream.chunk_by(fn {type, _}
[
{:dialogue, %{}},
{:info, %{}},
{:info, %{}},
{:info, %{}},
{:dialogue, %{}},
{:dialogue, %{}},
{:info, %{}}
...
]
我的最终目标是将对象分块,这样每个分块都以一个{:dialogue,%{}}
元组开始
最初我的代码如下:
stream
|> Stream.chunk_by(fn {type, _} -> type end) # [[dialogue], [info, info], [dialogue]...]
|> Stream.chunk(2) # [[[dialogue], [info, info, info]], [[dialogue], ...]]
但我很快意识到,当一行有两个对话元组时,这种情况就不复存在了——这两个元组的分组不再产生预期的结果
理想情况下,我希望创建一些工作方式如下:
chunk_when(list, fn({type, record}) -> type == :dialogue end) |> Enum.to_list
=> [[dialogue, info, info, info], [dialogue], [dialogue, info]...]
但我一直在研究如何使用流模块来实现这一点
必须调用Stream.transform/4
或Stream.resource/3
才能使其工作,但我无法理解
是相同的想法,但它适用于列表而不是流-它们有不同的API。来拯救:
defmodule A do
@input [
{:dialogue, %{}},
{:info, %{}},
{:info, %{}},
{:info, %{}},
{:dialogue, %{}},
{:dialogue, %{}},
{:info, %{}}]
def chunk_when(input \\ @input, type \\ :dialogue) do
input
|> Stream.map(& &1)
|> Stream.concat([nil]) # bah!
|> Stream.transform([], fn e, acc ->
case e do
nil -> {[acc], nil} # bah!
{^type, _} -> {(if Enum.empty?(acc), do: [], else: [acc]), [e]}
{_, _} -> {[], acc ++ [e]}
end
end)
end
end
IO.inspect Enum.to_list(A.chunk_when())
#⇒ [[dialogue: %{}, info: %{}, info: %{}, info: %{}],
# [dialogue: %{}],
# [dialogue: %{}, info: %{}]]
我愿意接受关于如何在两个明显肮脏的地方使它更优雅的建议:如何不将nil
附加到input
以捕获最后一个块,以及如何避免第一次出现愚蠢的if
:dialogue
。如果流中的第一个元组是{:info,%{}
?或者是否有一个假设第一个总是一个{:dialogue,%{}
?是的-所有信息都属于前面的对话,因此枚举总是以dialogue
元组开始。这只适用于列表,因为您正在对+[nil]
输入@Dogbert执行
操作,谢谢,将++
更改为String#concat/1
,但添加虚拟元素以模拟:halt
的解决方案似乎仍然太粗糙。有什么想法吗?很好!我知道会有一种使用Stream.transform/3的方法,但我不知道它是如何工作的,而且在野外也没有太多的例子。我已经用[nil]
承诺支持最后一个块,而不使用这种肮脏的黑客,让我们看看它会怎么样。