一般学习 Erlang 和递归。如何模拟计数器或累加器?

Learning Erlang in general and recursion. How to emulate a counter or acumulator?

-module(tut).
-export([folders/1]).
-export([main/0,incr/1]).

main() -> 
    Folders = folders("C:/Users/David/test"),
    LCounter = [0],
    pretty_print(Folders, 0, LCounter),
    ok.

folders(PATH) ->
    {_, DD} = file:list_dir(PATH), 
    A = [H || H <- DD, filelib:is_dir(PATH ++ "/" ++ H) =:= true], %%please note the "/" is added here
    %io:format("Path: ~p, A: ~p~n", [PATH, A]), 
    case A of
        [] ->   %%Base case, i.e. folder has no sub-folders -> stop here
                {PATH, []}; 
         _ ->   %%Recursive case, i.e. folder has sub-folders -> call @folders
                {PATH, [folders(PATH ++ "/" ++ H2) || H2 <- A]}
    end.

pretty_print(Folders, Depth, LCounter) ->

    {CurrrentFolder, ListSubfolders} = Folders,
    SignTemp = lists:duplicate(Depth, "-"),
    case Depth of
        0 -> Sign = SignTemp;
        _ -> Sign = "|" ++ SignTemp
    end,

    io:format("Depth ->~p", [ io_lib:format("~p", [Depth])]),
    io:format("| Lcounter ->~p", [LCounter]),
    io:format("~s~s~n", [Sign, CurrrentFolder]),
    

    [pretty_print(Subfolder, Depth+1,map(fun tut:incr/1, LCounter))|| Subfolder <- ListSubfolders].


    map(_, []) -> [];
    map(F, [H|T]) -> [F(H)|map(F,T)].
    incr(X) -> X + 1.

我对Erlang还很陌生,才学了两三天,很难改变范式。

我试图做的是一个计数器(在本例中为 Lcount)。其实我以为在每次调用函数时它都会增加,但我意识到它就像变量深度一样工作。

另一方面,我不明白为什么每次调用时深度都不会增加。

它是如何工作的?我错过了什么吗?

我无法尝试在 for 或 while 循环中使用 c++(增量)变量。

我认为学习这种不错的函数式语言对我来说会有点困难。

日志结果如下:

我想在这个练习中做的是简单地计算路径中的所有文件夹和子文件夹。这只是一个练习,但我很难理解和完成它。最重要的是因为我根本不会说流利的语言。

甚至可能有一个函数可以做到这一点,但我想尝试使用我所知道的一点点来获得它

首先,在 Erlang 中我们会处理很多列表数据类型。一个起点是熟悉 Erlang 文档中的简明 lists function
回到问题,文件夹就像一棵树,需要从根走到所有的树枝。

count_folder(Folders, AccumulatorIn) ->
    {_CurrentFolder, ListSubfolders} = Folders,
    case ListSubfolders of
        [] ->   %%Base case, i.e. folder has no sub-folders -> return 1 (count of CurrentFolder is always 1) + Accumulator from previous calculation
                1 + AccumulatorIn;
         _ ->   %%Recursive case, i.e. folder has sub-folders -> count for each ListSubfolders recursively
                CountListSubfolder = lists:foldl(fun count_folder/2, 0, ListSubfolders),
                1 + AccumulatorIn + CountListSubfolder
    end.

如何从主函数调用:

...
Cnt = count_folder(Folders, 0),
io:format("Count: ~p~n", [Cnt]), 
...

我添加这个答案来完成@Agus 的答案。

当您开始使用 Erlang 时,熟悉递归是个好主意。它是 Erlang 的核心,尽管事实上 Erlang 的库在很多情况下都会处理它:列表、映射、gb_trees、gen_server、gen_statem ... 因此在很多情况下它似乎消失了。

一开始,我建议您在掌握递归和尾递归的概念之前避免使用这些库。 然后,正如 Agus 所说,您应该深入研究众多 Erlang 库,它们已经解决了您可能遇到的大部分常见问题。

在您的代码中,您正在使用列表理解。这是一种优雅的语法(在我看来),但我认为它不符合您的目标(掌握递归),并且在您使用它的特定情况下,它隐藏了您真正在做什么:

[H || H <- DD, filelib:is_dir(Path ++ "/" ++ H) =:= true],

这里要过滤目录,我觉得lists:filter/2更明确(即使在这种情况下理解语法很容易理解):

lists:filter(fun(X) -> filelib:is_dir(filename:join(Path,X)) end, DD),

在我的回答结束时,我建议你使用“手工制作”递归的代码

filterDirs(DD,Path,[]),

{Path, [folders(Path ++ "/" ++ H2) || H2 <- A]},

您想将函数 folders/1 映射到 A 的每个元素,列表:map/2 正是这样做的

{Path, lists:map(fun(X) -> folders(filename:join(Path,X)) end , A)},

最后,

[pretty_print(Subfolder, Depth+1,Count + 1)|| Subfolder <- ListSubfolders],

列表推导不是你所期望的,就像在前面的例子中,它只是将函数 pretty_print 映射到列表 A 的每个元素上,而你想在每个元素上累积。函数列表:foldl/3 执行此操作(它需要修改 pretty_print 函数)

lists:foldl(fun(X,Acc) -> pretty_print(X, Depth+1, Acc + 1) end, Count, ListSubfolders);

在我的例子中,我提出了一个不同的手工版本,它也以更标准的树表示形式打印结果。

pretty_print(ListSubfolders,Depth+1,{Count+1, Sign ++ AddSign}).


-module(tut).
-export([main/1]).

main(Path) -> 
    Folders = folders(Path),
    pretty_print(Folders, 0, {1," "},true) - 1.

folders(Path) ->
    {ok, DD} = file:list_dir(Path),
    Dirs = filterDirs(DD,Path,[]),
    {Path,folders(Dirs,Path,[])}.

% I split the pretty print function in 2 parts. The first one takes care of the tuple and prints
% the current folder. It calls the second part to handle the sub-folder list,
% incrementing both the depth and count and evaluates the new "Sign"
% It needs to know if this tuple was the last of the sub-folder list where it belonged
% to adapt the "Sign" value 
pretty_print({CurrrentFolder, ListSubfolders}, Depth, {Count,Sign},Last) ->
    io:format("Depth -> ~3.B| Count -> ~3.B~s+--~s~n",
              [Depth,Count,Sign, filename:basename(CurrrentFolder)]),
    AddSign = case Last of 
        true -> "   ";
        false -> "|  "
    end,    
    pretty_print(ListSubfolders,Depth+1,{Count+1, Sign ++ AddSign}).

% The second part iterates on each sub-folder, it returns the Count value on completion
% and uses it for next element.
pretty_print([H|T],Depth,{Count,Sign}) ->
    NewCounter = pretty_print(H,Depth,{Count,Sign},T == []),
    pretty_print(T,Depth,{NewCounter,Sign});
pretty_print([],_,{Count,_}) ->
    Count.
 
filterDirs([],_,R) -> lists:reverse(R);
filterDirs([H|T],Path,R) ->
    case filelib:is_dir(filename:join(Path,H)) of
        true ->
            filterDirs(T,Path,[H|R]);
        false ->
            filterDirs(T,Path,R)
    end.

folders([],_,R) -> lists:reverse(R);
folders([H|T],Path,R) -> folders(T,Path,[folders(filename:join(Path,H))|R]).

它给出:

1> c(tut).                                                     
{ok,tut}
2> tut:main("C:/temp").
Depth ->   0| Count ->   1 +--temp
Depth ->   1| Count ->   2    +--t1
Depth ->   1| Count ->   3    +--testdisk-7.2-WIP
Depth ->   2| Count ->   4    |  +--63
Depth ->   2| Count ->   5    |  +--platforms
Depth ->   1| Count ->   6    +--u_test
Depth ->   2| Count ->   7       +--a_dir
Depth ->   2| Count ->   8       +--b_dir
8
3>