模式匹配Elixir中的列表中间

时间:2017-04-10 19:11:16

标签: pattern-matching elixir

有没有办法模式匹配[_head |中间| Elixir中的_last]?也就是说,我希望在列表中匹配至少包含三个内容(但通常更多),但只匹配除头部和最后一个元素之外的所有内容。

示例:

    def match_middle([_head | middle | _last]) do
      IO.inspect middle
    end

    match_middle [1, 2, 3, 4, 5]
    >>> [2, 3, 4]

3 个答案:

答案 0 :(得分:4)

Elixir列表确实是缺点,可以追溯到Lisp的早期阶段。

iex(1)> foo = [1 ,2 , 3]
[1, 2, 3]
iex(2)> [ head | [ middle | tail ]] = foo
[1, 2, 3]
iex(3)> middle
2

因此,当我们将其写为[1,2,3]时,它确实是[1, [2,[3,[]]]]

但是,这真的不能涵盖你所要求的内容。这样的事可能

def match_middle([_head | [ second | tail]]) when tail != [] do
  flip_tail = Enum.reverse(tail)
  [end | rest] = flip_tail
  [second | Enum.reverse(rest) ]
end

答案 1 :(得分:2)

我可以解决没有模式匹配的问题,因为你的情况非常具体 - 你假设middle是除了第一个和最后一个元素之外的所有东西,但是分割运算符以不同的方式工作 - 它分割第一个元素的列表和其他所有内容为tail

在你的情况下,我会创建用于匹配中间的私有函数:

defp get_middle_from_list(list) do
  list 
  |> Stream.drop(1)
  |> Stream.drop(-1)
  |> Enum.to_list()
end

并使用它

get_middle_from_list [1, 2, 3, 4, 5]
> [2, 3, 4]

当然,如果列表包含少于3个元素,它将返回[]

答案 2 :(得分:1)

正如您所看到的,List.pop_at/3本身是使用递归,:lists.reverse和头部访问实现的。也就是说,无法直接访问列表中的最后一个元素。

另一方面,如果您担心,列表中的N个元素不能超过,那么直接模式匹配仍然可以实现:

defmodule MatchInTheMiddle do
  @n 10
  Enum.each(0..@n, fn i ->
    @match_clause Enum.map(0..i, fn j -> {:"p#{j}", [], Elixir} end)
    def match_middle(unquote(
      [{:_head, [], Elixir}] ++ @match_clause ++ [{:_last, [], Elixir}])
    ), do: IO.inspect unquote(@match_clause)
  end)
end

而且,是的,短期和长期列表的优雅后备:

def match_middle([]), do: []
def match_middle([_]), do: []
def match_middle([_head | tail]) do
  with [_tail | middle] <- :lists.reverse(tail), do: middle
end

MatchInTheMiddle.match_middle([1,2,3,4])
#⇒ [2,3]

这并不像你预期的那样优雅,但在某些情况下它可能会有所帮助,因为这些条款是编译到模式匹配和大量数据,这是更多高效Enum操作。