Perl Weekly Challenge: Week 233

Challenge 1:

Similiar Words

You are given an array of words made up of alphabets only.

Write a script to find the number of pairs of similar words. Two words are similar if they consist of the same characters.

Example 1
Input: @words = ("aba", "aabb", "abcd", "bac", "aabc")
Output: 2

Pair 1: similar words ("aba", "aabb")
Pair 2: similar words ("bac", "aabc")
Example 2
Input: @words = ("aabb", "ab", "ba")
Output: 3

Pair 1: similar words ("aabb", "ab")
Pair 2: similar words ("aabb", "ba")
Pair 3: similar words ("ab", "ba")
Example 3
Input: @words = ("nba", "cba", "dba")
Output: 0

We can just about manage this one as a one-liner.

@*ARGS.map({$_.comb.unique.sort.join}).combinations(2).grep({@$_[0]eq@$_[1]}).elems.say

(Full code on Github.)

We take the command-line arguments (from @*ARGS) and transform them with .map() using .comb() to split each one into individual characters, find the unique ones with .unique(), sort them in alphabetical order with (.sort()) and join them back up again with .join().

Next we form all the pairs of elements of that transformed array with .combinations(2) and then use .grep() to filter out those pairs whose constituents are the same as each other. .elems() counts these matching pairs and .say() prints the result.

The core of the Perl version is also quite concise but it needs some extra help. I have unique() and combinations() functions from previous challenges that I used to emulate things Raku has but are missing from Perl's standard library.

say scalar grep { $_->[0] eq $_->[1] }
combinations([ map { join q{}, sort (unique([ split //, $_ ])); } @ARGV], 2);

(Full code on Github.)

Challenge 2:

Frequency Sort

You are given an array of integers.

Write a script to sort the given array in increasing order based on the frequency of the values. If multiple values have the same frequency then sort them in decreasing order.

Example 1
Input: @ints = (1,1,2,2,2,3)
Ouput: (3,1,1,2,2,2)

'3' has a frequency of 1
'1' has a frequency of 2
'2' has a frequency of 3
Example 2
Input: @ints = (2,3,1,3,2)
Ouput: (1,3,3,2,2)

'2' and '3' both have a frequency of 2, so they are sorted in decreasing order.
Example 3
Input: @ints = (-1,1,-6,4,5,-6,1,4,1)
Ouput: (5,-1,4,4,-6,-6,1,1,1)

Raku has a method called .classify() which is quite handy and even in its most basic usage is helpful for this task. The line below creates a hash whose keys are unique integers in @ints and whose values are a list of each occurrence of that integer (i.e. its' frequency).

my %freq = @ints.classify({$_});

Then, (ignoring say() for a minute)...

say q{(},

...we take the keys of %freq...

    %freq
        .keys

... and sort them in ascending order according to the size of their value lists using .elems(). In the event there are keys with the same frequency (they are not lesser or greater,) we sort again in descending numeric order of the keys.

        .sort({ %freq{$^a}.elems <=> %freq{$^b}.elems || $^b <=> $^a })

Then we replace the keys with their values taking care to make one "flat" array with |...

        .map({ | %freq{$_} })

...and join the elements with commas. The say() at the top is to surround the result with parentheses to make it look like the output in the examples.

        .join(q{,}),
    q{)};

(Full code on Github.)

This is the perl version. I had to provide my own classify() function but other than that Perl has everything needed.

my %freq = classify(@ARGV);

say q{(},
    (
        join q{,},
        map { @{$freq{$_}} }
        sort { scalar @{$freq{$a}} <=> scalar @{$freq{$b}} || $b <=> $a }
        keys %freq
    ),
q{)};

(Full code on Github.)