Erlang递归结束循环

时间:2014-12-01 18:34:41

标签: recursion erlang

我刚刚开始学习Erlang,因为我发现没有for循环我尝试用递归重新创建一个:

display(Rooms, In) ->
    Room = array:get(In, Rooms)
    io:format("~w", [Room]),
    if
        In < 59 -> display(Rooms, In + 1);
        true -> true
    end.

使用此代码,我需要显示Rooms中每个数组的内容(false或true),直到达到数字59。然而,这会创建一个奇怪的代码,显示所有房间内容大约60次(?)。当我删除if语句并且只放入递归代码时,它正在工作,除了异常错误:Bad Argument。

所以基本上我的问题是如何正确地结束我的“for循环”。

提前致谢!

2 个答案:

答案 0 :(得分:7)

嗯,这段代码被重写并且没有粘贴。在Room = array:get(In, Rooms)之后缺少冒号。 Bad argument错误可能是这样的:

exception error: bad argument
in function  array:get/2 (array.erl, line 633)
in call from your_module_name:display/2

这意味着,您使用错误参数调用array:get/2:要么Rooms不是数组,要么使用索引超出范围。第二个更可能是原因。您正在检查是否:

In < 59

然后再次调用display,所以它将达到58,评估为true并调用:

display(Rooms, 59)

太多了。

还有其他一些事情:

  1. io:format/2中,通常最好使用~p代替~w。它完全相同,但有漂亮的打印,因此更容易阅读。

  2. 在Erlang if是不自然的,因为它评估了守卫,其中一个必须匹配或者你得到错误......这真的很奇怪。

  3. case更具可读性:

    case In < 59 of
        false -> do_something();
        true -> ok
    end
    

    case中,你经常写一些东西,总是匹配:

    case Something of
        {One, Two} -> do_stuff(One, Two);
        [Head, RestOfList] -> do_other_stuff(Head, RestOfList);
        _ -> none_of_the_previous_matched()
    end
    

    下划线在模式匹配中非常有用。

    1. 在函数式语言中,您永远不必担心像索引这样的细节!数组模块有map函数,它将函数和数组作为参数,并在每个数组元素上调用给定的函数。
    2. 所以你可以这样写代码:

      display(Rooms) ->
          DisplayRoom = fun(Index, Room) -> io:format("~p ~p~n", [Index, Room]) end,
          array:map(DisplayRoom, Rooms).
      

      虽然这并不完美,因为除了调用io:format/2并显示内容之外,它还将构造新的数组。 io:format在完成后返回原子ok,因此您将获得58个ok个原子的数组。还有array:foldl/3,没有那个问题。

      如果您不必随机访问,最好只使用列表。

      Rooms = lists:duplicate(58, false),
      DisplayRoom = fun(Room) -> io:format("~p~n", [Room]) end,
      lists:foreach(DisplayRoom, Rooms)
      

      如果您对高阶函数不满意。列表允许您轻松编写带有函数子句的递归算法:

      display([]) ->                 % always start with base case, where you don't need recursion
          ok;                        % you have to return something
      display([Room | RestRooms]) -> % pattern match on list splitting it to first element and tail
          io:format("~p~n", [Room]), % do something with first element
          display(RestRooms).        % recursive call on rest (RestRooms is quite funny name :D)
      

      总结一下 - 不要在Erlang中写forloops:)

答案 1 :(得分:6)

这是对递归循环定义的一般误解。您要检查的内容称为“基本条件”或“基本案例”。这最容易通过匹配来处理:

display(0, _) ->
    ok;
display(In, Rooms) ->
    Room = array:get(In, Rooms)
    io:format("~w~n", [Room]),
    display(In - 1, Rooms).
然而,这是相当单一的。而不是使用手工制作的递归函数,像折叠或地图这样的东西更常见。

除此之外,大多数人可能已选择将房间表示为集合或列表,并使用列表操作对其进行迭代。当手写时,“基本案例”将是一个空列表而不是0:

display([]) ->
    ok;
display([Room | Rooms]) ->
    io:format("~w~n", [Room]),
    display(Rooms).

对于像foreach这样的列表操作,我们会再次避免这样做:

display(Rooms) ->
    lists:foreach(fun(Room) -> io:format("~w~n", [Room]) end, Rooms).

有些人真的不喜欢这种方式在线阅读lambdas。 (在这种情况下,我发现它可读,but the larger they get the more likely the are to become genuinely distracting。)完全相同函数的替代表示:

display(Rooms) ->
    Display = fun(Room) -> io:format("~w~n", [Room]) end,
    lists:foreach(Display, Rooms).

这本身可能会被传递,支持使用列表推导作为迭代的简写:

_ = [io:format("~w~n", [Room]) | Room <- Rooms].

试图产生副作用时,我真的认为lists:foreach/2是出于语义原因的最佳选择。

我认为您遇到的部分困难是您选择使用一个相当不寻常的结构作为您的第一个Erlang程序的基础数据,它可以执行任何操作(数组不经常使用,并且在功能上不是非常惯用的语言)。首先尝试使用列表 - 它并不可怕 - 一些成语和其他代码示例以及关于列表处理和函数式编程的一般性讨论将更有意义。

<强>等待!还有更多......

我没有处理你有不规则房间布局的情况。我们的假设总是在一个漂亮的均匀网格中布局 - 当你进入真正有趣的东西时(从因为地图不规则或因为拓扑结构很有趣),情况永远不会如此。

这里的主要区别在于,不是简单地携带[Room]列表,其中每个Room值是表示Room的状态的单个值,您可以将房间的状态值包装在元组中其中还包含一些关于该状态的额外数据,例如它的位置或坐标,名称等等。(你知道,“元数据” - 今天这是一个超载,充满嗡嗡声的术语,我讨厌这样说。)

假设我们需要在房间所在的三维空间中保持坐标,并且每个房间都有一个占用者列表。在数组的情况下,我们将数组除以布局的尺寸。 10 * 10 * 10空间的数组索引从0到999,每个位置都可以通过类似于

的操作找到
locate({X, Y, Z}) -> (1 * X) + (10 * Y) + (100 * Z).

,每个Room的值为[Occupant1, occupant2, ...]

定义这样一个数组然后将其任意大的区域标记为“不可用”以给出不规则布局的印象,然后解决试图模拟3D世界的问题将是一个真正的烦恼。

相反,我们可以使用列表(或类似列表)来表示房间集,但Room值现在是元组:Room = {{X, Y, Z}, [Occupants]}。您可能有一个额外的元素(或十个!),如房间的“名称”或其他一些状态信息或其他,但坐标是您可能获得的最确定的真实身份。要获得房间状态,您可以像以前一样做,但标记您正在查看的元素:

display(Rooms) ->
    Display =
        fun({ID, Occupants}) ->
            io:format("ID ~p: Occupants ~p~n", [ID, Occupants])
        end,
    lists:foreach(Display, Rooms).

要做一些比顺序打印更有趣的事情,你可以用一个函数替换Display的内部,该函数使用坐标绘制图表上的房间,检查Occupants的空列表或完整列表(使用模式匹配,不要在程序上做到!),或者你可能想到的任何其他东西。