Erlang Central

Control breaks Reading File

From ErlangCentral Wiki

Problem:

We want to read to the end a File bearing information about the Activity of Branches, held by Members of an Association, and print a Report on the Standard Output, printing all the Lines, and doing a Control-Break, with a printout of Totals, whenever the Member changes. Each Line of the File represents a Branch, and is formatted in fixed-size Fields this way: 1) Member Code: columns [1-5], 2) Branch Code: columns [7-11], 3) Number of Active Accounts of a Branch: columns [13-15], 4) Number of Transactions done in that Branch: columns [17-20], 5) Start Date of the Period, 6) End Date of the Period, 7) Member Acronym, 8) Branch Acronym

Such Lines are sorted with primary key 1) and secondary key 2). The Totals required at every Control-Break on 1) are:

a) Number of Branches held by that Member b) Total of Active Accounts c) Total of Transactions done.

Solution:

One possible solution is the following. Since we want read the File to the End, supposing the number of lines is unknown, we choose reading the lines by a recursive function with an increasing recursion index which starts by 0; the function will test the End of File condition at every recursion.

The main program must open the File, start the recursive function, and close the File when the function returns. The recursive function take_one() is started with this list of parameters: File Pointer, Initial (0) Recursion Index, Initial (impossible) Member Code, Initial Number of Branches, Initial Account Total, Initial Transaction Total.

body() ->
        {ok, Fin} = file:open("branches.txt", read),
        take_one(Fin, 0, "FAKE", 0, 0, 0),
        file:close(Fin),
        io:format("All the Data Above are Fancy.~n").

Now look at the function take_one(). It bears a "case" condition testing the return value of Built-In function io:get_line(). If it is an End of File mark, the function take_one() does (through the function do_tot()), the last Totals for the last Member (parameter Previous), then ends, leaving to the main body() to close the File. If io:get_line() returns a valid data line, it is put on the buffer Buf, and the first 5-character Field (Member Code) is put in the variable Memb. Now the Control-Break condition is tested: Memb /= Previous. If so, a call to function do_tot() shows the printout of Totals on the Standard Output. Since Erlang does not allow to change Previous, like procedural languages usually do, the local variable Curr is bound to the value of Memb if a Control-Break occurred, and to the value of Previous if not. In similar fashion are used the local variables Quack, TAcc, TTra, which are bound to the corresponding parameters, or to 0 in case of Control-Break. Then, the line Buffer Buf is printed, and take_one() calls itself recursively, after getting from function do_sing() a pair of numbers, representing the Accounts and Transactions given by Buf. Those values are used to increment variables TAcc and TTra in the new call to take_one().

take_one(FId, N, Previous, Quan, QAcc, QTra) ->
case io:get_line(FId, []) of
        eof ->
                do_tot(Previous, Quan, QAcc, QTra);
        Buf ->
                Memb = string:substr(Buf, 1, 5),
                if Memb /= Previous ->
                        do_tot(Previous, Quan, QAcc, QTra),
                        Curr = Memb, Quack = 0, TAcc = 0, TTra = 0,
                        io:fwrite("Active Branches of Member: ~s~n", [Curr]);
                true ->
                        Curr = Previous, Quack = Quan, TAcc = QAcc, TTra = QTra
                end,
                io:fwrite("~s", [Buf]),
                {NuAcc, NuTra} = do_sing(Buf),
                take_one(FId, N+1, Curr, Quack+1, TAcc+NuAcc, TTra+NuTra)
end.

The function do_sing() gets two fields from the Line Buffer, converts them to numbers, and returns them in a Tuple to the calling function take_one().

do_sing(X) ->
        Accch = string:substr(X, 13, 3), Trach = string:substr(X, 17, 4),
        Acc = list_to_integer(Accch), Tra = list_to_integer(Trach),
        {Acc, Tra}.

The function do_tot() treats the initial "FAKE" code, which obviously produce an initial Control-Break, for printing a simple general header. The appropriated values of member's code and totals are used to make up and output every summary printout, when a true Control-Break occurs.

do_tot(Pre, Qn, Qacc, Qtra) ->
if Pre == "FAKE" ->
        io:fwrite("~nAssociation Members and their Active Branches~n~n");
true ->
        Qncar = integer_to_list(Qn),
        Qacch = integer_to_list(Qacc),
        Qtrah = integer_to_list(Qtra),
Outs = Pre++" owns "++Qncar++" Branches "++Qacch++" Accounts "++Qtrah++" Transactions",
        io:fwrite("~n~s~n~n", [Outs])
end.