假设我有一个单词列表,其中一个关键字,在这个例子中,“停止”,划出完整的句子:
["Hello", "from", "Paris", "stop", "Weather", "is", "sunny", "stop", "Missing", "you", "stop"]我想把它变成:
[["Hello", "from", "Paris"], ["Weather", "is", "sunny"], ["Missing", "you"]]我知道我可以用String.split来处理字符串,但理想情况下,我想学习如何使用基本的函数构造来解决上述问题,比如头尾递归等,但我不知道如何从哪里开始积累中间列表。
发布于 2016-08-22 20:44:25
下面是一个使用模式匹配的简单尾递归实现:
defmodule Main do
def split_on(list, on) do
list
|> Enum.reverse
|> do_split_on(on, [[]])
|> Enum.reject(fn list -> list == [] end)
end
def do_split_on([], _, acc), do: acc
def do_split_on([h | t], h, acc), do: do_split_on(t, h, [[] | acc])
def do_split_on([h | t], on, [h2 | t2]), do: do_split_on(t, on, [[h | h2] | t2])
def main do
["Hello", "from", "Paris", "stop", "Weather", "is", "sunny", "stop", "Missing", "you", "stop"]
|> split_on("stop")
|> IO.inspect
end
end
Main.main输出:
[["Hello", "from", "Paris"], ["Weather", "is", "sunny"], ["Missing", "you"]]发布于 2016-08-22 20:53:38
您可以使用chunk_by/2
["Hello", "from", "Paris", "stop", "Weather", "is", "sunny", "stop", "Missing", "you", "stop"]
|> Enum.chunk_by(fn(x) -> x != "stop" end)
|> Enum.reject(fn(x) -> x == ["stop"] end)性能
出于好奇,我想对这个问题的实现性能进行基准测试。基准是每个实现的10万个调用,我运行了3次。如果有人感兴趣,以下是结果:
0.292903s = 0.316024s \x{e76f}\x{e76f}\x{e76f} chunk_by 0.168113s \x{e76f} 0.152456s \x{e76f} 0.151854s = Main.main (@Dogbert's answer) 0.167387s \x{e76f} 0.148059s \x{e76f} 0.143763s = chunk 0.177080s =0.180632 s= 0.185636s = splitter (@stephen
发布于 2016-08-22 21:48:05
这几乎是Enum.chunk_by/2所做的。
def chunk_by(可枚举,有趣) 每个元素上可被拆分,每个元素都返回一个新的值。
但是chunk_by不会丢弃任何元素,所以我们可以将它与Enum.filter/2结合起来。
list = [1, 2, 3, :stop, 4, 5, 6, :stop, 7, 8, :stop] # analogous to your list
list
|> Enum.chunk_by(&(&1 == :stop))
# at this point, you have [[1,2,3], [:stop], [4,5,6], [:stop], [7,8], [:stop]]
|> Enum.reject(&(&1 == [:stop]))
# here you are: [[1,2,3], [4,5,6], [7,8]]第二种方法是使用Enum.reduce/3。由于我们在前面建立了累加器,将我们找到的第一个元素推到后面,所以在缩小列表之前倒转列表是有意义的。否则,我们将得到一个反向列表的反向列表。
我们可能会得到空列表,比如示例列表中的最后一个:stop。因此,我们再一次过滤列表的末尾。
list
|> Enum.reverse
|> Enum.reduce([[]], fn # note: the accumulator is a nested empty list
:stop, acc -> [[] | acc] # element is the stop word, start a new list
el, [h | t] -> [[el | h] | t] # remember, h is a list, t is list of lists
end)
|> Enum.reject(&Enum.empty?/1)最后,让我们自己介绍一下列表,并构建一个累加器。如果这让你想起了reduce版本,那不是巧合。
defmodule Stopword do
def chunk_on(list, stop \\ :stop) do
list
|> Enum.reverse
|> chunk_on(stop, [[]])
end
defp chunk_on([], _, acc) do
Enum.reject(acc, &Enum.empty?/1)
end
defp chunk_on([stop | t], stop, acc) do
chunk_on(t, stop, [[] | acc])
end
defp chunk_on([el | t], stop, [head_list | tail_lists]) do
chunk_on(t, stop, [[el | head_list] | tail_lists])
end
end我们使用不需要用户担心累加器的公共函数的公共模式,并将输入传递给带有累加器的私有arity+1函数。因为我们正在建立一个列表列表,所以在累加器中开始一个空列表是很有用的。这样,当累加器为空时,我们不需要特殊情况。
我们在遍历列表之前将其反转,就像我们对reduce所做的那样,就像在完成之后拒绝空列表一样。同样的理由也适用。
我们使用模式匹配来识别停止词。停止词标志着一个新列表的开始,所以我们添加一个新的空列表并丢弃停止词。
一个固定的单词简单地放在第一个列表的前面,在我们的列表中。这些条形和括号的语法有点不灵活。
https://stackoverflow.com/questions/39087950
复制相似问题