💡 100. Bubble sort in Perl 6

💡 100. Bubble sort in Raku

N. B. Perl 6 has been renamed to Raku. Click to read more.


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”

Leave a Reply