Hackerrank solutions: Python 3 and Perl 6 (part 1)

I recently started at a new company, for which I will have to write Python 3 code. To make sure I still know how to do basic stuff in Python, I started to work on some Hackerrank challenges. In this post, I will show solutions to some challenges to show the differences. I hope that I can show that Perl doesn’t have to be the "write only" language that many people make it out to be.

Note

I am much more proficient in the Perl 6 programming language than in Python (2 or 3), so I might not always use the most optimal solutions in the Python variants. Suggestions are welcome via email, though I most likely won’t update this post with better solutions. I ofcourse also welcome feedback on the Perl 6 solutions!

Challenges

The challenges covered in this post are the warmup challenges you are recommended to solve when you make a new account. The code around the function I’m expected to solve won’t be included, as this should be irrelevant (for now). Additionally, I may rename the sub to conform to kebab-case, as this is more readable (in my opinion), and allowed in Perl 6.

Solve Me First

This challenge is just a very simple example to introduce how the site works. It required me to make a simple a + b function.

def solveMeFirst(a,b):
    return a+b

The Perl 6 variant isn’t going to very different here.

sub solve-me-first ($a, $b) {
    $a + $b
}

For those not familiar with Perl 6, the $ in front of the variable names is called a Sigil, and it signals that the variable contains only a single value.

You may have noticed that there’s also no return in the Perl 6 variant of this example. In Perl 6, the last statement in a block is also the implicit return value (just like in Perl 5 or Ruby).

Simple Array Sum

For this challenge I had to write a function that would return the sum of a list of values. Naturally, I wanted to use a reduce function, but Python 3 does not support these. So I wrote it with a for loop instead.

def simpleArraySum(ar):
    sum = 0

    for i in ar:
        sum += i

    return sum

Perl 6 does have a reduce function, so I would use that to solve the problem here.

sub simple-array-sum (@ar) {
    @ar.reduce(sub ($a, $b) { $a + $b })
}

Here you can see a different sigil for @ar. The @ sigil denotes a list of scalars in Perl 6. In most other languages this would simply be an array.

This code can be written even shorter, however. Perl 6 has reduction meta-operators. This allows you to put an operator between brackets, like [+], to apply a certain operator as a reduce function.

sub simple-array-sum (@ar) {
    [+] @ar
}
Note

After publishing this post I have learned that both Python 3 and Perl 6 have a .sum function that can also be called on the array, simplifying the code in both languages.

Compare the Triplets

This challenge provides you with 2 lists of 3 elements each. The lists should be compared to one another, and a "score" should be kept. For each index, if the first list contains a larger number, the first list’s score must be incremented. Similarly, if the second list contains a larger number on that index, the second list’s score must be incremented. If the values are equal, do nothing.

def compareTriplets(a, b):
    scores = [0, 0]

    for i in range(3):
        if a[i] > b[i]:
            scores[0] += 1

        if a[i] < b[i]:
            scores[1] += 1

    return scores

I learned that Python 3 has no ++ operator to increment a value by 1, so I had to use += 1 instead.

sub compare-triplets (@a, @b) {
    my @scores = [0, 0];

    for ^3 {
        @scores[0]++ if @a[$_] > @b[$_];
        @scores[1]++ if @a[$_] < @b[$_];
    }
}

In Perl 6, the ^3 notation simply means a range from 0 to 3, non-inclusive, so 0, 1, 2, meaning it will loop 3 times. The $_ is called the topic, and in a for loop it is the current element of the iteration.

Both of these loops could use a continue (or next in Perl 6) to skip the second if in case the first if was true, but for readability I chose not to.

Note

After publishing this post I learned that Python 3 also supports the inline if syntax, just like Perl 6, so I could’ve used this in Python 3 as well.

A Very Big Sum

In this challenge, you need to write the function body for aVeryBigSum, which gets an array of integers, and has to return the sum of this array. Both Python 3 and Perl 6 handle the large integers transparently for you, so I was able to use the same code as I used for the simple array sum challenge.

def aVeryBigSum(ar):
    sum = 0

    for i in ar:
        sum += i

    return sum

And for Perl 6 using the [+] reduce meta-operation.

sub a-very-big-sum (@ar) {
    [+] @ar
}

Plus Minus

The next challenge gives a list of numbers, and wants you to return the fractions of its elements which are positive, negative or zero. The fractions should be rounded down to 6 decimals. I made a counter just like in the Compare the Triplets challenge, and calculated the fractions and rounded them at the end.

def plusMinus(arr):
    counters = [0, 0, 0]

    for i in arr:
        if (i > 0):
            counters[0] += 1
            continue

        if (i < 0):
            counters[1] += 1
            continue

        counters[2] += 1

    for i in counters:
        print("%.6f" % (i / len(arr)))

For the Perl 6 solution, I went for a given/when, map and the fmt function to format the fractions.

sub plus-minus (@arr) {
    my @counters = [0, 0, 0];

    for @arr -> $i {
        given $i {
            when * > 0 { @counters[0]++ }
            when * < 0 { @counters[1]++ }
            default    { @counters[2]++ }
        }
    }

    @counters.map({ $_.fmt("%.6f").say });
}

You may notice a number of statements do not have a terminating ; at the end. In Perl 6, this is not needed if it’s the last statement in a block (any code surrounded by a { and }.

The given/when construct is similar to a switch/case found in other languages (but not Python, sadly), but uses the smartmatch operator implicitly to check if the statements given to when are True. The * is the Whatever operator, which in this case will get the value of $i.

Lastly, he $_ in the map function is similar to inside a for loop, it’s the current element. Since the code given to map is inside a block, there’s no need for a ; after say either.

Staircase

This challenge gives you an integer 𝓃, and you’re tasked with "drawing" a staircase that is 𝓃 high, and 𝓃 wide at the base. The staircase must be made using # characters, and for the spacing you must use regular spaces.

It seems that in Python, you must specify the i in part oft the for i in range. Since I don’t really care for the value, I assigned it to _.

def staircase(n):
    for i in range(1, n + 1):
        for _ in range(n - i):
            print(" ", end="")

        for _ in range(i):
            print("#", end="")

        print("")

In Perl 6, there’s also a print function, which is like say, but does not append a \n at the end of the string. The for loop in Perl 6 allows for just a range to operate as expected. The .. operator creates a range from the left-hand side up to the right hand side, inclusive.

sub staircase ($n) {
    for 1..$n -> $i {
        print(" ") for 0..($n - $i);
        print("#") for ^$i;
        print("\n");
    }
}

Mini-Maxi Sum

Here you will be given 5 integers, and have to calculate the minimum and maximum values that can be calculated using only 4 of them.

I sort the array, and iterate over the first 4 values to calculate the sum and print it. I then do the same but sort it in reverse for the sum of the 4 highest values.

def miniMaxSum(arr):
    arr.sort()
    sum = 0

    for i in range(4):
        sum += arr[i]

    print(str(sum) + " ", end="")

    arr.sort(reverse=True)
    sum = 0

    for i in range(4):
        sum += arr[i]

    print(str(sum))

Perl 6 has immutable lists, so calling sort on them will return a new list which has been sorted. I can call reverse on that list to get the highest number at the top instead. head allows me to get the first 4 elements in a functional way. You’ve already seen the meta-reduce operator [+], which will get me the sum of the 4 elements I got from head. I wrap the calculation in parenthesis so I can call print on the result immediately.

sub mini-maxi-sum (@arr) {
    ([+] @arr.sort.head(4)).print;
    print(" ");
    ([+] @arr.sort.reverse.head(4)).print;
}

Birthday Cake Candles

In this challenge, you’re given a list of numbers. You must find the highest number in the list, and return how often that number occurs in the list.

It’s fairly straightforward, I keep track of the current largest value as size, and a count that I reset whenever I find a larger value than I currently have.

def birthdayCakeCandles(ar):
    size = 0
    count = 0

    for i in ar:
        if i > size:
            size = i
            count = 0

        if i == size:
            count += 1

    return count

The Perl 6 variant does not differ in how it solves the problem, apart from having a very different syntax of course.

sub birthday-cake-candles (@ar) {
    my ($size, $count) = (0, 0);

    for @ar {
        if ($_ > $size) {
            $size = $_;
            $count = 0;
        }

        $count++ if $size == $_;
    }

    $count;
}
Note

On IRC, someone showed me a clean solution in Python 3: return ar.count(max(ar)). This feels like a much cleaner solution than what I had created.

Time Conversion

This is the final challenge of this section on Hackerrank, and also this post. You’re given a timestamp in 12-hour AM/PM format, and have to convert it to a 24-hour format.

I split the AM/PM identifier from the actual time by treating the string as a list of characters and taking two slices, one of the last two characters, and one of everything but the last two characters. Then I split the time into parts, and convert the first part (hours) to integers for calculations. Next I set the hours to 0 if it’s set to 12, and add 12 hours if the timestamp was post meridiem. Finally, I convert the hours back to a string with leading zeroes, and join all the parts together to form a timestamp again.

def timeConversion(s):
    meridiem = s[-2:]
    hours = int(s[:2])
    rest = s[2:-2]

    if (hours > 11):
        hours = 0

    if (meridiem.lower() == "pm"):
        hours += 12

    return ("%02d:%s" % (hours, rest))

The Perl 6 solution again doesn’t differ much from the Python solution in terms of the logic it’s using to get the result. The biggest difference is that in Perl 6, strings can’t be accessed as lists, so I use the substr method to extract the parts that I want. The first one starts at *-2, which means 2 places before the end. The others get a Range as argument, and will get the characters that exist in that range.

sub time-conversion ($s) {
    my $meridiem = $s.substr(*-2);
    my $hours = $s.substr(0..2).Int;
    my $rest = $s.substr(2..*-2);

    $hours = 0 if $hours > 11;
    $hours += 12 if $meridiem.lc eq "pm";

    sprintf("%02d:%s", $hours, $rest);
}

The .Int method converts the Str object into an Int object, so we can perform calculations on it. The eq operator checks specifically for string equality. Since Perl 6 is a gradually typed programming language, there’s a dedicated operator to ensure that you’re checking string equality correctly.

Wrap-up

These challenges were just the warm-up challenges I was given after creating a new account and choosing Python as a language to use. I intend to write up more posts like this, for the near future I’ll stick to Python 3 challenges since I want to get better at that specific language for work.

This is also the first post in which I have tried this format to show off two languages side-by-side, and to highlight differences in how you can accomplish certain (relatively simple) tasks with them. If you have suggestions to improve this format, do not hesitate to contact me. I am always open for feedback, preferably via email. You can find my contact details on the homepage.