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

oug3syen  于 2022-12-08  发布在  Erlang
关注(0)|答案(2)|浏览(148)
-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.

I'm quite new to Erlang, I've only been learning for two or three days and it's hard for me to change the paradigm.
What I was trying to do is a counter (in this case Lcount). Actually I thought that in each call to the function it would increase, but I realize that it works like the variable Depth.
On the other hand I do not understand how it is possible that Depth does not also increase in each call.
How does it work? Am I missing something?
I have trouble not trying to use c++ (increment) variables in for or while loops.
I think it is going to be a bit hard for me to learn this nice functional language.
Here are the log results:

What I want to do in this exercise is to simply count all the folders and subfolders from a path. It is just an exercise, but I am having a hard time understanding and finishing it. More than anything because I am not fluent in the language at all.
There might even be a function to do that, but I want to try and get it using what little I know

zzoitvuj

zzoitvuj1#

To start with, in Erlang we will deal a lot with list data type. One starting point is to familiarize with the concise lists function from Erlang documentation.
Back to the question, folders are like tree, so need to walk down the path from root to all branches.

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.

Howto call from main function:

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

7vhp5slm2#

I add this answer to complete @Agus one's.
As you are starting with Erlang, it is a good idea to get familiar with recursion. It is central in Erlang, despite of the fact that Erlang's libraries take care of it in numerous situations : lists, maps, gb_trees, gen_server, gen_statem ... and therefore it seems to disappear in many cases.
At the very beginning, I advice you to avoid the usage of those libraries until you master the concept of recursion and tail recursion. Then, as Agus says, you should dig into the numerous Erlang libraries, they already solve most of the common problems you could have.
In your code, you are using list comprehension. It is an elegant syntax (in my opinion) but I think that it does not fit to your goal (mastering recursion) and, in the particular cases you used it, it hides what you are really doing:

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

Here you want to filter the directories, I think that lists:filter/2 is more explicit (even if the comprehension syntax is easy to understand in this case):

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

At the end of my answer, I propose you a code were I have used a "hand made" recursion

filterDirs(DD,Path,[]),

With

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

You want to map the function folders/1 on each element of A, lists:map/2 does exactly this

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

Finally, with

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

The list comprehension does not what you expect, like in previous case it simply maps the function pretty_print on each element of the list A, while you want to accumulate on each element. The function lists:foldl/3 does this (it needs a modification of the pretty_print function)

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

In my example I propose a different hand made version that also print the result in a more standard tree representation.

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]).

It gives:

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>

相关问题