【问题标题】:Case to check when list is empty rather then recursion?检查列表何时为空而不是递归的情况?
【发布时间】:2014-10-03 13:34:06
【问题描述】:

我试图检查一个列表是否为空而不是递归地捕获模式,这是进入 Erlang 的正确方法还是我只是走错了路,模式匹配是最好的方法捕捉列表是否已清空?

     calculate([Head|Tail], pi, x, y) ->
         ...calculations of Head being sent to a list...
         case Tail == [] of
            false ->
               calculate(Tail, pi, x, y)
         end.

或者如果列表为空,我应该只在计算时进行模式匹配?

【问题讨论】:

    标签: erlang


    【解决方案1】:

    代码错误

    一般做法是使用带有模式匹配的函数子句。它的工作原理与案例一样,并且被认为更具可读性。它修复了您在实施中遇到的一个错误:

    首先你的代码可以用这种方式重写。

    calculate([Head|Tail], pi, x, y) ->
      %% ... actual calculations ...
      calculate( Tail, pi, x, y);
    calculate([], pi, x, y) ->
      %% you need to return something here, but you don't
    

    如您所见,其中一个子句不返回任何内容,这在 Erlang 中是不允许的(编译期间失败)。您的实现完全一样。 case 就像 Erlang 中的任何东西都必须返回一些值(因为它是函数中的 lase 语句,所以这个值将从函数返回)。由于case 需要返回一些东西,它需要匹配它的一个子句。在大多数情况下,因为Tail == [] 将返回false,所以这不是问题。但最后递归调用,当Tail 为空列表时,Tail == [] 将返回true,而case 将不匹配任何内容。在 Erlang 中,这将导致(准确地说是抛出或退出)case_clause 错误。所以你的实现总是会失败。

    要修复它,您需要确保您的情况下始终有匹配的东西,像这样

    case Tail == [] of
      false ->
        calculate(Tail, pi, x, y)
      true ->
        %% return something 
    end.
    

    也可以这样写

    case Tail of
      [] ->
        %% return something sane
      _ -> 
        calculate(Tail, pi, x, y)
    end.
    

    _ 可以匹配任何东西,并且工作起来有点像else 是其他一些语言。最后它可以用函数子句来编写,就像我之前展示的那样,但返回的是这个合理的值。


    编辑

    返回一个值

    如果您现在仔细查看我们的代码,我们只返回一个值;最后一个递归调用(我称之为“理智”的那个)。如果您想考虑所有递归调用的所有计算,您需要以某种方式累积它们。为此,我们将使用Acc 变量

    calculate([Head|Tail], pi, x, y, Acc) ->
      %% ... actual calculations with result assigned to Res variable ...
      NewAcc = [Res | Acc]
      calculate(Tail, pi, x, y, NewAcc);
    calculate([], pi, x, y, Acc) ->
      Acc.
    

    在每个递归调用中,我们将计算 Res 添加到累加器 Acc,并将更新后的列表发送到下一级递归。最后,当我们的输入列表为空时(我们处理了所有数据),我们只返回整个累加器。我们需要做的就是确保当calculate 第一次被调用时,它被调用为空列表Acc。这可以通过新的(有些旧的)函数来完成

    calculate(List, pi, x, y) ->
       calculate(List, pi, x, y, _Acc = []).
    

    现在我们可以导出calculate/4 并将calculate/5 保密。

    【讨论】:

      【解决方案2】:

      模式匹配。这是正确的事情。

      它也更有效率。它还可以防止你养成只接受任何类型的变量的习惯,在你的函数中途发现你收到的甚至不是一个列表(哎呀!)。模式匹配(以及使用某些类型的保护)也是 Dialyzer 检查成功类型的核心——这对您现在可能或无关紧要,但一旦您开始开发有客户的软件,肯定会如此。

      不过,最重要的是,学习利用模式匹配可以教你编写更小的函数。编写一个可以做所有事情的带有 bajillion 参数的巨大函数当然是可能的,甚至在许多其他语言中也很常见,但是模式匹配会向你说明为什么一旦你开始编写这是一个坏主意你的火柴盒。这将以我无法描述的方式帮助你;它会渗透到你对程序的看法,而你一开始并不欣赏它;它将消除嵌套条件的混乱(因为它们不存在);它会教你停止在任何地方编写参数错误检查代码

      【讨论】:

        【解决方案3】:

        添加一个带有空列表的子句,如果不可能,添加一个带有单个元素列表的子句:

        func([H],P,X,Y) ->
             do_something(H,P,X,Y);
        func([H|T],P,X,Y) ->
             do_something(H,P,X,Y),
             func(T,P,X,Y).
        

        请注意,这将因输入列表为空而失败。

        看看你是否可以使用其中一个函数 list:map/2 或 lists:foldl/3 或 list comprehension...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-01-12
          • 1970-01-01
          • 1970-01-01
          • 2010-12-04
          • 1970-01-01
          • 2014-06-21
          • 1970-01-01
          相关资源
          最近更新 更多