summaryrefslogtreecommitdiff
path: root/_posts/2018-09-13-hackerrank-solutions-python3-and-perl6-part-1.adoc
blob: 6dd01a68f38da4029eac24c8f449805eca34ebda (plain)
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
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
---
tags: Hackerrank Perl6 Python Python3 Programming Raku
description: >
  A number of solutions to Hackerrank challenges in both the Python 3 and the
  Perl 6 programming languages. Compare the results and see which language
  works best for you!
---
= Hackerrank solutions: Python 3 and Perl 6 (part 1)
:toc: preamble

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 https://www.hackerrank.com/[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
https://www.hackerrank.com/domains/algorithms?filters%5Bsubdomains%5D%5B%5D=warmup[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
https://en.wikipedia.org/wiki/Letter_case#Special_case_styles[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.

[source,py3]
----
def solveMeFirst(a,b):
    return a+b
----

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

[source,perl6]
----
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 https://docs.perl6.org/language/glossary#index-entry-Sigil[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.

[source,py3]
----
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.

[source,perl6]
----
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
https://docs.perl6.org/language/operators#index-entry-%5B%2B%5D_%28reduction_metaoperators%29[reduction
meta-operators]. This allows you to put an operator between brackets, like
`[+]`, to apply a certain operator as a reduce function.

[source,perl6]
----
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.

[source,py3]
----
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.

[source,perl6]
----
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.

[source,py3]
----
def aVeryBigSum(ar):
    sum = 0

    for i in ar:
        sum += i

    return sum
----

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

[source,perl6]
----
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.

[source,py3]
----
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.

[source,perl6]
----
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
https://docs.perl6.org/language/operators#index-entry-smartmatch_operator[smartmatch
operator] implicitly to check if the statements given to `when` are `True`. The
`*` is the https://docs.perl6.org/type/Whatever[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 `_`.

[source,py3]
----
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.

[source,perl6]
----
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.

[source,py3]
----
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.

[source,perl6]
----
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.

[source,py3]
----
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.

[source,perl6]
----
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.

[source,py3]
----
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
https://docs.perl6.org/type/Range[`Range`] as argument, and will get the
characters that exist in that range.

[source,perl6]
----
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
https://docs.perl6.org/routine/eq[__string equality__]. Since Perl 6 is a
https://en.wikipedia.org/wiki/Gradual_typing[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 link:/[homepage].