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]
承诺支持最后一个块,而不使用这种肮脏的黑客,让我们看看它会怎么样。