2017-01-18 3 views
3

У меня есть поток держит перечисление из двух элементов кортежей, такие как:Чанкинг Потока с пользовательской функцией отрывов

[ 
    {: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], ...]] 

Но вскоре я понял, что это упал, когда было два dialogue кортежи подряд - пара комков больше не дали ожидаемого результата.

В идеале я хотел бы создать какой-то метод, который работает, как:

chunk_when(list, fn({type, record}) -> type == :dialogue end) |> Enum.to_list 
=> [[dialogue, info, info, info], [dialogue], [dialogue, info]...] 

Но я застрял на том, как сделать это с помощью модуля Stream.

Должно быть какое-то указание Stream.transform/4 или Stream.resource/3, что заставило бы его работать, но я не могу понять это.

This question - та же идея, но она работает со списками, а не потоками, которые имеют разные API.

+0

Что произойдет, если первый кортеж в потоке «{: info,% {}}'? Или есть предположение, что первым всегда будет '{: dialog,% {}}'? – nietaki

+0

Yep - вся информация относится к предыдущему диалогу, поэтому перечисление всегда начинается с кортежа 'dialog'. – sevenseacat

ответ

1

Stream.transform/3 приходит на помощь:

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 появление.

+0

Это работает только со списками, поскольку вы выполняете '++ [nil]' '' input'. – Dogbert

+0

@Dogbert действительно, спасибо, изменил '++' на 'String # concat/1', но решение с добавлением фиктивного элемента для имитации': halt' по-прежнему кажется слишком хакивым. Есть идеи? – mudasobwa

+0

приятно! Я знал, что был бы способ использовать Stream.transform/3, но не мог обернуться вокруг того, как он работает, и примеров в дикой природе не так уж много. – sevenseacat

 Смежные вопросы

  • Нет связанных вопросов^_^