如何在列表与单个元素上进行模式匹配

Posted

技术标签:

【中文标题】如何在列表与单个元素上进行模式匹配【英文标题】:How to pattern match on list vs single element 【发布时间】:2019-11-17 03:46:40 【问题描述】:

我试图了解如何在 Erlang 中对单个元素与列表进行模式匹配:

guarded(T) when T>5 ; T<3 -> 3+T;
guarded([X,Y]) when X>3,Y>3 ->X+1,Y+1;
guarded([X,_|[T,_]]) when X rem 2==0, T rem 2 =/= 1-> [T,X];
guarded(_)->"something else".



guarded([1,2,3]).  -> goes into case 1 , how can i make sure it doesn't (and goes to last case)

** 异常错误:计算算术表达式时发生错误 在函数 main:guarded/1

当我想对单个元素进行模式匹配时,我需要将第一个案例放在哪里。我的意思是我想要一个适用于单个元素和通配符模式的案例(最后一个表达式)。

【问题讨论】:

除了已经给出的答案之外,有趣的是,erlang 中的类型是有序的,因此任何类型的两个变量之间的比较总是有效的,并且将返回 true 或 false。例如,任何列表都大于任何数字。这就是为什么你的第一个语句总是匹配的原因,除非 T 是 3 到 5 之间的数字。 您发表评论前 4 小时已在答案中描述了这一点,因此您再次看起来像是在抄袭人们的答案。 没关系,对我来说这是有价值的信息,发现列表可以与数字进行比较,并且默认情况下被视为大于数字。 【参考方案1】:

你可以给你的守卫添加一个is_list/1检查来检查T不是一个列表:

guarded(T) when not is_list(T) andalso (T>5 orelse T<3) -> 3+T;
guarded([X,Y]) when X>3, Y>3 ->X+1,Y+1;
guarded([X,_|[T,_]]) when X rem 2==0, T rem 2 =/= 1-> [T,X];
guarded(_)->"something else".

或者您可以使用is_number/1is_integer/1 分别检查T 是数字还是整数:

guarded(T) when is_number(T) andalso (T>5 orelse T<3) -> 3+T;
guarded([X,Y]) when X>3, Y>3 ->X+1,Y+1;
guarded([X,_|[T,_]]) when X rem 2==0, T rem 2 =/= 1-> [T,X];
guarded(_)->"something else".

【讨论】:

我认为优先级不存在;这意味着(not is_list(T) andalso T&gt;5) orelse T&lt;3 而不是所需的not is_list(T) andalso (T&gt;5 orelse T&lt;3) 在第一种情况下,我仍然会使用, 而不是andalso,这只是为了让评论更清晰。【参考方案2】:

作为 Vinoski 答案的替代方案,您还可以将不太具体的模式移到末尾,以便首先匹配列表:

guarded([X,Y]) when X>3,Y>3 ->X+1,Y+1;
guarded([X,_|[T,_]]) when X rem 2==0, T rem 2 =/= 1-> [T,X];
guarded(T) when T>5 ; T<3 -> 3+T;
guarded(_)->"something else".

但在这种特定情况下,它不会很好地工作,因为像 guarded([1,1]) 这样的东西仍然不会匹配前两个分支,但会匹配 T 一个;这是因为 Erlang allows comparing any two values 只是认为列表大于数字。

【讨论】:

所以如果没有办法进行模式匹配,我很好奇Erlang 中的多重性是如何测试的?我的意思是像is_list(上面提到的)这样的方法是如何实现的? 这个函数实际上在 Erlang 中很容易实现:is_list([]) -&gt; true; is_list([_|_]) -&gt; true; is_list(_) -&gt; false.。但这不适用于例如is_number。实际上,它们都是用 C 代码而不是 Erlang 实现的。【参考方案3】:

我的意思是像is_list(上面提到的)这样的方法是如何实现的?

使用erlang,你可以像这样实现is_list()

-module(my).
-compile([export_all]).

islist([]) ->     % empty list
    true;
islist([_|_]) ->  % non-empty list
    true;
islist(_) ->      % anything else
    false.

当你调用一个函数时,erlang 从定义中的第一个函数子句开始,并尝试将函数调用中指定的参数与函数定义中的参数相匹配。如果没有匹配,erlang 然后尝试下一个函数子句。当找到匹配项时,执行相应的函数体。如果没有任何函数子句匹配,那么您会收到 function_clause 错误。

在外壳中:

~/erlang_programs$ erl
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.4  (abort with ^G)

1> c(my).
ok,my

2> my:islist(3).
false

3> my:islist(1, 2).
false

4> my:islist([1, 2]).
true

5> my:islist([]).
true

6> my:islist("abc"). 
true

在第 6 行,您应该知道"abc" 是整数列表[97, 98, 99] 的简写。在 erlang 中,双引号字符串是包含双引号字符串中字符的整数代码点的列表的简写。

【讨论】:

以上是关于如何在列表与单个元素上进行模式匹配的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Scala 3 枚举上进行模式匹配

如何使用模式匹配在scala中获取一个nonEmpty列表?

在erlang中列出尾部模式匹配

如何在模式匹配中拆分列表?

主题模式

如何改进 Python 中列表的模式匹配