forked from szabgab/perlmaven.com
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhow-to-calculate-balance-of-bank-accounts-in-csv-file-using-perl.tt
More file actions
450 lines (331 loc) · 12.8 KB
/
how-to-calculate-balance-of-bank-accounts-in-csv-file-using-perl.tt
File metadata and controls
450 lines (331 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
=title How to calculate the balance of bank accounts in a CSV file, using Perl?
=timestamp 2012-08-04T20:45:56
=indexes CSV, split
=status show
=author szabgab
=index 1
=archive 1
=feed 1
=comments 1
=social 1
=abstract start
One of the readers of the <a href="/how-to-read-a-csv-file-using-perl">How to read a CSV file using Perl?</a> article
has sent me a CSV file and a question:
<b>How to Calculate and display total balance in each account using hash in perl. Without using parse function?</b>
Let's see how can we handle such a request?
=abstract end
<code>
TranID,Date,AcNo,Type,Amount,ChequeNo,DDNo,Bank,Branch
13520,01-01-2011,5131342,Dr,5000,,,,
13524,01-01-2011,5131342,Dr,1000,,416123,SB,Ashoknagar
13538,08-01-2011,5131342,Cr,1620,19101,,,
13548,17-01-2011,5131342,Cr,3500,19102,,,
13519,01-01-2011,5522341,Dr,2000,14514,,SBM,Hampankatte
13523,01-01-2011,5522341,Cr,500,19121,,,
13529,02-01-2011,5522341,Dr,5000,13211,,SB,Ashoknagar
13539,09-01-2011,5522341,Cr,500,19122,,,
13541,10-01-2011,5522341,Cr,2000,19123,,,
</code>
At first it was not clear where is he stuck or if he made any progress at all.
In order to help him I had to try to figure out both what he wanted to reach and the where he is stuck.
I asked for the code he already wrote, and got this script:
<code lang="perl">
#!/usr/bin/perl
print "Content-type:text/html\n\n";
my $sum;
my $sum1;
my $sum2;
open(FILEHANDLE, "<banktran.csv") or die "Could not open 'banktran.csv' $!\n";
while (my $line = <FILEHANDLE>) {
chomp $line;
my @fields = split "," , $line;
if ($fields[2] eq 5131342) {
if ($fields[3] eq Dr) {
$sum1 += $fields[4];
} else {
$sum2 += $fields[4];
}
$sum = $sum1-$sum2;
print "Total Balance of Account Number is Rs.$sum\n";
}
}
close(FILEHANDLE);
</code>
Besides some obvious beginner issues I started to understand, that probably he wants to create a separate report for
each account - the third column - <b>AcNo</b> is probably the account number.
The amount is in the 5th column under the title <b>Amount</b>.
As I can see the 4th column indicates if the type of the transaction. A little search indicates that Dr would be debit
and Cr would be credit, though in the code that seems to be the opposite.
The first sentence in the question seems to indicate that he already understand he needs to use hashes,
instead of the scalar <hl>$sum</hl> variables, but it is yet unclear to him how.
The second sentence, <b>Without using parse function?</b> seems to indicate to me that for some reason the reader
cannot use the Text::CSV module that has the parse method. That's unfortunate, as that is the right tool in the
general case of parsing and reading CSV files, but in many corporate settings installing a module from CPAN is
difficult. Especially to someone who is new to Perl.
Assuming the CSV file is simple - no quoted separators, no embedded newlines - we can handle it with a call
to the <hl>split</hl> function.
The code he sent me is reasonable for someone starting to use Perl, let's see how can we improve it
and how can we try to implement what he needed?
<h2>Running the code</h2>
Before trying to improve the code, let's see if it runs, and what does it do? The script is saved
as <b>banktran.pl</b> and the csv file as <b>banktran.csv</b>
<b>perl bantran.pl</b>
<code>
Content-type:text/html
Total Balance of Account Number is Rs.5000
Total Balance of Account Number is Rs.6000
Total Balance of Account Number is Rs.4380
Total Balance of Account Number is Rs.880
</code>
Now that we see it does something we can make some improvements:
<h2>use strict and use warnings</h2>
First of all, I very strongly recommend every Perl script to start with the two statements of the
safety-net. I know that I can waste a lot of valuable time searching for bugs that these
two would catch, so I don't want to be without them.
<code lang="perl">
use strict;
use warnings;
</code>
It should come right after the sh-bang. If we add this to the above code and try to run it again we get
the following:
<code>
Bareword "Dr" not allowed while "strict subs" in use at banktran.pl line 18.
Execution of banktran.pl aborted due to compilation errors.
</code>
<a href="/barewords-in-perl">Bareword not allowed while "strict subs" in use</a>
is one of the common warnings described in <a href="/perl-tutorial">Perl Maven tutorial</a>.
We need to put single-quotes <hl>'</hl> around the string <b>Dr</b>
Running the script again we get the following:
<code>
Content-type:text/html
Use of uninitialized value $sum2 in subtraction (-) at banktran.pl line 23, <FILEHANDLE> line 2.
Total Balance of Account Number is Rs.5000
Use of uninitialized value $sum2 in subtraction (-) at banktran.pl line 23, <FILEHANDLE> line 3.
Total Balance of Account Number is Rs.6000
Total Balance of Account Number is Rs.4380
Total Balance of Account Number is Rs.880
</code>
The <a href="/use-of-uninitialized-value">Use of uninitialized value</a> warning
is another common warning in Perl. It means the <hl>$sum2</hl> was undef in line 23.
<code lang="perl">
$sum = $sum1-$sum2;
</code>
We should probably initialize the variables to 0. It is not always necessary, but it can lead
to cleaner code. The resulting code so far looks like this:
<code lang="perl">
#!/usr/bin/perl
use strict;
use warnings;
print "Content-type:text/html\n\n";
my $sum = 0;
my $sum1 = 0;
my $sum2 = 0;
open(FILEHANDLE, "<banktran.csv") or die "Could not open 'banktran.csv' $!\n";
while (my $line = <FILEHANDLE>) {
chomp $line;
my @fields = split "," , $line;
if ($fields[2] eq 5131342) {
if ($fields[3] eq 'Dr') {
$sum1 += $fields[4];
} else {
$sum2 += $fields[4];
}
$sum = $sum1-$sum2;
print "Total Balance of Account Number is Rs.$sum\n";
}
}
close(FILEHANDLE);
</code>
<h2>Using open the "modern" way</h2>
I put the word "modern" in quotes because this is available since 2000 so it is not really
new, but still many people learn the old style first.
There is an article why one
<a href="/open-files-in-the-old-way">should not open files in the old way in Perl</a>,
I won't repeat it here, I'll just fix the code:
<code lang="perl">
#!/usr/bin/perl
use strict;
use warnings;
print "Content-type:text/html\n\n";
my $sum = 0;
my $sum1 = 0;
my $sum2 = 0;
my $filename = 'banktran.csv';
open(my $FILEHANDLE, '<', $filename) or die "Could not open '$filename' $!\n";
while (my $line = <$FILEHANDLE>) {
chomp $line;
my @fields = split "," , $line;
if ($fields[2] eq 5131342) {
if ($fields[3] eq 'Dr') {
$sum1 += $fields[4];
} else {
$sum2 += $fields[4];
}
$sum = $sum1-$sum2;
print "Total Balance of Account Number is Rs.$sum\n";
}
}
close($FILEHANDLE);
</code>
As you can see the I changed <hl>FILEHANDLE</hl> to be the lexical scalar <hl>$FILEHANDLE</hl>,
using 3 parameters in the <hl>open</hl> function and also put the name of the file in a variable.
This last step is important for two reasons:
<ol>
<li>It will make it easier to pass the name of the file as a parameter, if we need it.</hl>
<li>We won't fall in the trap of changing the name in the <hl>open()</hl> call, and leaving the old name in the
<hl>die()</hl> call and getting confused by the error message.</li>
</ol>
<h2>Better variable names</h2>
The fact that we are using and array called <hl>@fields</hl> and indexes in that array
makes it unclear what kind of values are in those field.
Quickly, can you remember what is in $fields[2]? I cannot. So instead of using the @fields array
we could use variables with better names and write:
<code lang="perl">
my ($id, $date, $account, $type, $amount, $cheque, $dd, $bank, $branch)
= split "," , $line;
</code>
This turned the above line a bit longer but will make the rest of the code more readable.
This also makes us creates some unnecessary variables.
<a href="/advanced-perl-maven-e-book">Advanced Perl developers</a> could make it nicer by using an array slice:
<code lang="perl">
my ($account, $type, $amount) = (split "," , $line)[2, 3, 4];
</code>
The loop will look like this:
<code lang="perl">
while (my $line = <$FILEHANDLE>) {
chomp $line;
my ($account, $type, $amount) = (split "," , $line)[2, 3, 4];
if ($account eq 5131342) {
if ($type eq 'Dr') {
$sum1 += $amount;
} else {
$sum2 += $amount;
}
$sum = $sum1-$sum2;
print "Total Balance of Account Number is Rs.$sum\n";
}
}
</code>
<h2>Eliminate temporary variables</h2>
As I can see the <hl>$sum1</hl> and <hl>$sum2</hl> variables are used only to hold
the values that either need to be added to the $sum or deducted from it. We don't really need them.
We could add to <hl>$sum</hl>, or deduct from it inside the condition:
<code lang="perl">
if ($account eq 5131342) {
if ($type eq 'Dr') {
$sum += $amount;
} else {
$sum -= $amount;
}
</code>
Let's see and try the full code again, before the big operation.
<code lang="perl">
#!/usr/bin/perl
use strict;
use warnings;
print "Content-type:text/html\n\n";
my $sum = 0;
my $filename = 'banktran.csv';
open(my $FILEHANDLE, '<', $filename) or die "Could not open '$filename' $!\n";
while (my $line = <$FILEHANDLE>) {
chomp $line;
my ($account, $type, $amount) = (split "," , $line)[2, 3, 4];
if ($account eq 5131342) {
if ($type eq 'Dr') {
$sum += $amount;
} else {
$sum -= $amount;
}
print "Total Balance of Account Number is Rs.$sum\n";
}
}
close($FILEHANDLE);
</code>
<h2>Show the total for all the accounts</h2>
Right now, only one specific account (id = 5131342) is summarized and it is done in a scalar variable.
Instead of this we would like to summarize all the accounts. The easiest way is to use a hash.
The account ids will be the keys and the sum will be the value.
<code lang="perl">
#!/usr/bin/perl
use strict;
use warnings;
print "Content-type:text/html\n\n";
my %sum;
my $filename = 'banktran.csv';
open(my $FILEHANDLE, '<', $filename) or die "Could not open '$filename' $!\n";
while (my $line = <$FILEHANDLE>) {
chomp $line;
my ($account, $type, $amount) = (split "," , $line)[2, 3, 4];
if ($type eq 'Dr') {
$sum{$account} += $amount;
} else {
$sum{$account} -= $amount;
}
print "Total Balance of Account Number $account is Rs.$sum{$account}\n";
}
close($FILEHANDLE);
</code>
In this code we don't need the <hl>if ($account eq 5131342)</hl> condition any more.
We can access the hash key directly, using the <hl>$account</hl> number as the key.
After running the script the result looks like this:
<code>
Content-type:text/html
Argument "Amount" isn't numeric in subtraction (-) at banktran.pl line 19, <$FILEHANDLE> line 1.
Total Balance of Account Number AcNo is Rs.0
Total Balance of Account Number 5131342 is Rs.5000
Total Balance of Account Number 5131342 is Rs.6000
Total Balance of Account Number 5131342 is Rs.4380
Total Balance of Account Number 5131342 is Rs.880
Total Balance of Account Number 5522341 is Rs.2000
Total Balance of Account Number 5522341 is Rs.1500
Total Balance of Account Number 5522341 is Rs.6500
Total Balance of Account Number 5522341 is Rs.6000
Total Balance of Account Number 5522341 is Rs.4000
</code>
The warning we get is due to the first line in the CSV file. Earlier we did not have
to care about it as we only dealt with rows where the account id was matching the selected
number, but now we have to skip that line. It's easy, just read the first row before the
<hl>while</hl> loop, and throw it away.
<code lang="perl">
<$FILEHANDLE>;
while (my $line = <$FILEHANDLE>) {
</code>
<h2>Total only at the end?</h2>
This could be the final version, but it is unclear to me if we really need to display the Balance after every row,
or only at the end. So let's make another change that will display the results only at the end.
We remove the <hl>print</hl> call from the <hl>while</hl> loop and add another loop at the end, going over all the
accounts and displaying the account status:
<code lang="perl">
foreach my $account (sort keys %sum) {
print "Total Balance of Account Number $account is Rs.$sum{$account}\n";
}
</code>
The full code
<code lang="perl">
#!/usr/bin/perl
use strict;
use warnings;
print "Content-type:text/html\n\n";
my %sum;
my $filename = 'banktran.csv';
open(my $FILEHANDLE, '<', $filename) or die "Could not open '$filename' $!\n";
<$FILEHANDLE>;
while (my $line = <$FILEHANDLE>) {
chomp $line;
my ($account, $type, $amount) = (split "," , $line)[2, 3, 4];
if ($type eq 'Dr') {
$sum{$account} += $amount;
} else {
$sum{$account} -= $amount;
}
}
close($FILEHANDLE);
foreach my $account (sort keys %sum) {
print "Total Balance of Account Number $account is Rs.$sum{$account}\n";
}
</code>
There is only one little thing that still bothers me. Why do we print Content-type
at the beginning of the code? Is this supposed to run as a CGI script?
If no, then we could remove that line.
If this is a CGI script then we should probably print real HTML out. At least we should
print pre tags around the report.