我刚开始学习Erlang,因为我发现没有for循环我尝试用递归重新创建一个:
display(Rooms, In) ->
Room = array:get(In, Rooms)
io:format("~w", [Room]),
if
In < 59 -> display(Rooms, In + 1);
true -> true
end.
Run Code Online (Sandbox Code Playgroud)
使用此代码,我需要显示Rooms中每个数组的内容(false或true),直到达到数字59.然而,这会创建一个奇怪的代码,显示所有房间内容大约60次(?).当我删除if语句并且只放入递归代码时,它正在工作,除了异常错误:Bad Argument.
所以基本上我的问题是我如何正确地结束我的"for循环".
提前致谢!
嗯,这段代码被重写而不是粘贴.之后缺少冒号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
Run Code Online (Sandbox Code Playgroud)
这意味着,您array:get/2
使用错误参数调用:要么Rooms不是数组,要么使用索引超出范围.第二个更可能是原因.您正在检查是否:
In < 59
Run Code Online (Sandbox Code Playgroud)
然后再次调用display,所以它将达到58,评估为true并调用:
display(Rooms, 59)
Run Code Online (Sandbox Code Playgroud)
太多了.
还有其他几件事:
在io:format/2
通常最好是使用~p
替代~w
.它完全相同,但具有漂亮的打印,因此更容易阅读.
在Erlang if
是不自然的,因为它评估警卫,其中一个必须匹配或你得到错误......这真的很奇怪.
case
更具可读性:
case In < 59 of
false -> do_something();
true -> ok
end
Run Code Online (Sandbox Code Playgroud)
在case
你经常写的东西,总是匹配:
case Something of
{One, Two} -> do_stuff(One, Two);
[Head, RestOfList] -> do_other_stuff(Head, RestOfList);
_ -> none_of_the_previous_matched()
end
Run Code Online (Sandbox Code Playgroud)
下划线在模式匹配中非常有用.
所以你可以这样写你的代码:
display(Rooms) ->
DisplayRoom = fun(Index, Room) -> io:format("~p ~p~n", [Index, Room]) end,
array:map(DisplayRoom, Rooms).
Run Code Online (Sandbox Code Playgroud)
但这并不完美,因为除了调用io:format/2
和显示内容外,它还将构造新的数组.完成后io:format
返回atom ok
,所以你将获得58个ok
原子的数组.还有array:foldl/3
,没有那个问题.
如果您不必随机访问,最好只使用列表.
Rooms = lists:duplicate(58, false),
DisplayRoom = fun(Room) -> io:format("~p~n", [Room]) end,
lists:foreach(DisplayRoom, Rooms)
Run Code Online (Sandbox Code Playgroud)
如果您对高阶函数不满意.列表允许您轻松编写带有函数子句的递归算法:
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)
Run Code Online (Sandbox Code Playgroud)
总结一下 - 不要在Erlang中写forloops :)
这是对递归循环定义的一般误解.您要检查的内容称为"基本条件"或"基本案例".通过匹配最容易处理:
display(0, _) ->
ok;
display(In, Rooms) ->
Room = array:get(In, Rooms)
io:format("~w~n", [Room]),
display(In - 1, Rooms).
Run Code Online (Sandbox Code Playgroud)
然而,这是相当单一的.而不是使用手工制作的递归函数,像折叠或地图这样的东西更常见.
然而,更进一步,大多数人可能已经选择将房间表示为集合或列表,并使用列表操作迭代它.当手写时,"基本案例"将是一个空列表而不是0:
display([]) ->
ok;
display([Room | Rooms]) ->
io:format("~w~n", [Room]),
display(Rooms).
Run Code Online (Sandbox Code Playgroud)
对于像foreach这样的列表操作,本来可以避免使用哪一个:
display(Rooms) ->
lists:foreach(fun(Room) -> io:format("~w~n", [Room]) end, Rooms).
Run Code Online (Sandbox Code Playgroud)
有些人真的不喜欢用这种方式在线阅读lambdas.(在这种情况下,我发现它是可读的,但是越大它们越有可能真正分散注意力.)完全相同功能的替代表示:
display(Rooms) ->
Display = fun(Room) -> io:format("~w~n", [Room]) end,
lists:foreach(Display, Rooms).
Run Code Online (Sandbox Code Playgroud)
哪个本身可能会被放弃,转而使用列表理解作为迭代的简写:
_ = [io:format("~w~n", [Room]) | Room <- Rooms].
Run Code Online (Sandbox Code Playgroud)
但是,当只是试图产生副作用时,我认为这lists:foreach/2
是出于语义原因的最佳选择.
我认为你遇到的部分困难是你选择使用一个相当不寻常的结构作为你的第一个Erlang程序的基础数据,它可以做任何事情(数组不经常使用,在函数式语言中也不是很惯用).首先尝试使用列表 - 它并不可怕 - 一些习惯用法和其他代码示例以及关于列表处理和函数式编程的一般性讨论将更有意义.
等待!还有更多...
我没有处理你有不规则的房间布局的情况.假设总是所有东西都布置在一个漂亮的均匀网格中 - 当你进入真正有趣的东西时(从因为地图是不规则的或因为拓扑结构很有趣),情况永远不会如此.
这里的主要区别在于,不是简单地携带[Room]
每个Room
值是表示Room状态的单个值的列表,而是将房间的状态值包装在元组中,该元组还包含有关该状态的一些额外数据,例如其位置或者坐标,名称等等(你知道,"元数据" - 今天这是一个超载的,充满嗡嗡声的术语,我讨厌它.)
假设我们需要在房间所在的三维空间中保持坐标,并且每个房间都有一个占用者列表.在数组的情况下,我们将数组除以布局的尺寸.10*10*10空间的数组索引从0到999,每个位置都可以通过类似的操作找到
locate({X, Y, Z}) -> (1 * X) + (10 * Y) + (100 * Z).
Run Code Online (Sandbox Code Playgroud)
每个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).
Run Code Online (Sandbox Code Playgroud)
要做一些比顺序打印更有趣的事情,你可以Display
用一个函数替换内部函数,该函数使用坐标在图表上绘制房间,检查空列表或完整列表Occupants
(使用模式匹配,不要在程序上执行!) ,或者你可能想到的任何其他东西.