Hey everyone, let’s implement some algorithms in Perl 6.
The first one will be the classical Bubble sort. In essence, you scan the data from left to right and swap the two adjacent items if they are not ordered properly yet. Repeat until the whole data list is ordered.
Here’s the initial straightforward implementation:
sub bubble-sort(@data) { my Bool $done = False; while !$done { $done = True; for 1 .. @data.elems - 1 -> $n { if @data[$n - 1] > @data[$n] { (@data[$n - 1], @data[$n]) = (@data[$n], @data[$n - 1]); $done = False; } } } } my @data = 4, 5, 7, 1, 46, 78, 2, 2, 1, 9, 10; bubble-sort @data; say @data;
This implementation does in-place sorting, but you don’t need to declare the function argument as is rw
. Actually, the compiler will tell you that that is redundant and will stop further compilation:
For parameter '@data', '@' sigil containers don't need 'is rw' to be writable Can only use 'is rw' on a scalar ('$' sigil) parameter, not '@data'
Another place which I’d like to take a more precise look at is swapping the two array elements:
(@data[$n - 1], @data[$n]) = (@data[$n], @data[$n - 1]);
Now, as the program is ready and we can confirm it works correctly, let’s transform it to make it more expressive (although maybe a bit less efficient).
$ perl6 bubble-sort.pl6 [1 1 2 2 4 5 7 9 10 46 78]
The first step would be to simplify the swapping code. On both sides of the equals sign, we are working with the same two elements, with indices $n
and $n - 1
. It is possible to call the reverse
method on the list of two elements and assign it back to it:
(@data[$n - 1], @data[$n]).=reverse;
This can be simplified further by using an array slice:
@data[$n - 1, $n].=reverse;
As you can see, the parentheses are not needed any more. The only question, which you have to answer yourself, is whether to surround the .=
operator with spaces or not. On one hand, this is a method call (thus, no spaces), on another, it is an assignment (thus, spaces).
OK, what’s next? Of course, the if
statement can be written as a postfix condition, but there are two lines of code in the if
block. At this point, I can make a decision to trade between the beauty of code and the efficiency, and get rid of the Boolean $done
flag.
sub bubble-sort(@data) { for 1 .. @data.elems - 1 -> $n { if @data[$n - 1] > @data[$n] { @data[$n - 1, $n].=reverse; } } bubble-sort(@data) unless [<=] @data; }
This step also allowed to remove the while
block. At the end of the function, a recursive call is done (again, maybe less efficient, but who cares), and the condition to check if the array is already sorted is now explicit:
unless [<=] @data
After all that, it is now possible to append a postfix if
:
@data[$n - 1, $n].=reverse if @data[$n - 1] > @data[$n]
And even the postfix for
:
@data[$_ - 1, $_].=reverse if @data[$_ - 1] > @data[$_] for 1 .. @data.elems - 1;
Also, why not make the condition using both the slice and the reduction operator as we’ve already done in other parts of the function:
@data[$_ - 1, $_].=reverse if [>] @data[$_ - 1, $_] for 1 .. @data.elems - 1;
The next step is to reduce the range 1 .. @data.elems - 1
, which is OK but contains a bit too many elements, which can be removed. The -1
part can be replaced with an open range:
for 1 ..^ @data.elems;
After which the elems
call is not needed, and Perl 6 can do that for us:
for 1 ..^ @data;
And here’s the current state of the code:
sub bubble-sort(@data) { @data[$_ - 1, $_] .= reverse if [>] @data[$_ - 1, $_] for 1 ..^ @data; bubble-sort(@data) unless [<=] @data; } my @data = 4, 5, 7, 1, 46, 78, 2, 2, 1, 9, 10; bubble-sort @data; say @data;
Before we stop for now, here’s another modification that is possible. In general, recursion in Perl 6 can be implemented via multi-subs. Instead of branching inside the function, let the compiler do the job based on data:
multi sub bubble-sort(@data where [<=] @data) {} multi sub bubble-sort(@data where ![<=] @data) { @data[$_ - 1, $_] .= reverse if [>] @data[$_ - 1, $_] for 1 ..^ @data; bubble-sort(@data); }
If you don’t like the [<=]
check (as it should scan the whole list), here’s a brute-force version with another loop layer:
sub bubble-sort(@data) { for ^@data { @data[$_ - 1, $_] .= reverse if [>] @data[$_ - 1, $_] for 1 ..^ @data; } }
The two for
loops can be joined using the cross operator:
sub bubble-sort(@data) { for ^@data X 1 ..^ @data { my $n = $_[1]; @data[$n - 1, $n] .= reverse if [>] @data[$n - 1, $n]; } }
You are invited to work on this code further. I am really curious if and how this can be done even shorter (for example, how to re-use the slice).
The source codes of the above-shown programs are available on GitHub.
2 thoughts on “💡 100. Bubble sort in Perl 6”