Adding up sales per city from a small table, written with a Python, a Perl and a C accent, and as a native would write it.
The task
A shop has a small table of sales: one sale per line, with the city and the amount. Add up the amounts for each city and print the totals, with the cities in alphabetical order and two digits after the decimal point.
Every program below starts with the same table:
my $sales = q:to/END/;
Riga 12.50
Leiden 4.75
Turin 10.00
Riga 7.25
Leiden 3.10
Turin 2.35
Riga 0.40
END
q:to/END/ is a heredoc: everything up to the line END becomes one string, with a line break after each line. The answer is:
Leiden 7.85 Riga 20.15 Turin 12.35
With a Python accent
my $sales = q:to/END/;
Riga 12.50
Leiden 4.75
Turin 10.00
Riga 7.25
Leiden 3.10
Turin 2.35
Riga 0.40
END
my %totals;
for $sales.split("\n") -> $line {
if $line.trim eq '' {
next;
}
my @parts = $line.split(' ');
my $city = @parts[0];
my $amount = @parts[1].Num;
%totals{$city} = (%totals{$city} // 0) + $amount;
}
for %totals.keys.sort -> $city {
say("$city {sprintf('%.2f', %totals{$city})}");
}
What gives it away:
.split("\n")and then skipping the empty line. In Python,text.split("\n")leaves an empty string after the last line break, so you learn to check for it. Raku’s.linesdoes not give you that empty line..Num, which is Python’sfloat(). It works, but it turns the exact amount12.50into an approximate floating-point number.(%totals{$city} // 0) + $amount, which is Python’stotals.get(city, 0) + amount. Raku starts a missing value from zero by itself when you use+=.sprintfinside a string to format the number, the way an f-string does it, and parentheses around the argument ofsay.
With a Perl accent
my $sales = q:to/END/;
Riga 12.50
Leiden 4.75
Turin 10.00
Riga 7.25
Leiden 3.10
Turin 2.35
Riga 0.40
END
my %total;
for split /\n/, $sales {
next unless /\S/;
my ($city, $amount) = split /\s+/, $_;
%total{$city} += $amount;
}
for sort keys %total {
printf "%s %.2f\n", $_, %total{$_};
}
What gives it away:
splitwith a regex, called as a function, both for the lines and for the fields.next unless /\S/: a regex that silently matches against$_to skip empty lines.sort keys %totalread from right to left, as a chain of functions.printfwith\nat the end.
This is short and correct Raku. A Perl programmer reads it without effort. What is foreign is the shape: every step is a function wrapped around the next one, instead of methods following each other.
With a C accent
my $sales = q:to/END/;
Riga 12.50
Leiden 4.75
Turin 10.00
Riga 7.25
Leiden 3.10
Turin 2.35
Riga 0.40
END
my @lines = $sales.lines;
my @cities;
my @sums;
loop (my $i = 0; $i < @lines.elems; $i++) {
my $space = @lines[$i].index(' ');
my $city = @lines[$i].substr(0, $space);
my $amount = @lines[$i].substr($space + 1).Numeric;
my $j = 0;
while $j < @cities.elems && @cities[$j] ne $city {
$j++;
}
if $j == @cities.elems {
@cities.push($city);
@sums.push(0);
}
@sums[$j] += $amount;
}
loop (my $x = 0; $x < @cities.elems - 1; $x++) {
loop (my $y = 0; $y < @cities.elems - 1 - $x; $y++) {
if @cities[$y] gt @cities[$y + 1] {
my $city = @cities[$y];
@cities[$y] = @cities[$y + 1];
@cities[$y + 1] = $city;
my $sum = @sums[$y];
@sums[$y] = @sums[$y + 1];
@sums[$y + 1] = $sum;
}
}
}
loop (my $k = 0; $k < @cities.elems; $k++) {
printf("%s %.2f\n", @cities[$k], @sums[$k]);
}
What gives it away:
- Finding the space with
.indexand cutting the line with.substr, the way you would withstrchrand pointer arithmetic. - Two arrays side by side instead of a hash, and a
whileloop to search for the city. - A bubble sort written by hand, which has to swap both arrays in step.
loopwith a counter for every pass through a list.
Native
my $sales = q:to/END/;
Riga 12.50
Leiden 4.75
Turin 10.00
Riga 7.25
Leiden 3.10
Turin 2.35
Riga 0.40
END
my %totals;
for $sales.lines.map(*.words) -> ($city, $amount) {
%totals{$city} += $amount;
}
for %totals.sort {
say .key, ' ', .value.fmt('%.2f');
}
What changed
.linesgives the lines without the empty one at the end, and.wordscuts each line into its two fields.-> ($city, $amount)unpacks the two fields straight into two variables.+=on a missing key starts from zero, and turns the string'12.50'into a number on the way.- The amounts stay exact.
12.50becomes the fraction 25/2 (aRat), not a floating-point number, so adding money gives exactly the right total. %totals.sortsorts the pairs of the hash by key..keyand.valueare called on each pair, and.fmtformats the number.