Perl Weekly Challenge 189: Greater Character and Array Degree

These are some answers to the Week 189 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on November, 6, 2022 at 23:59). This blog post offers some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Greater Character

You are given an array of characters (a..z) and a target character.

Write a script to find out the smallest character in the given array lexicographically greater than the target character.

Example 1

Input: @array = qw/e m u g/, $target = 'b'
Output: e

Example 2

Input: @array = qw/d c e f/, $target = 'a'
Output: c

Example 3

Input: @array = qw/j a r/, $target = 'o'
Output: r

Example 4

Input: @array = qw/d c a f/, $target = 'a'
Output: c

Example 5

Input: @array = qw/t g a l/, $target = 'v'
Output: v

Note that when the array has no item larger than the target value, the behavior is undefined. I do not think that the solution provided for the example 5 is valid. I prefer to state clearly that no solution was found.

Greater Character in Raku

The greater-char subroutine uses grep to build the list of characters lexicographically greater than the target value and returns the smallest one. The ability to use two (or more) loop variables ($k and @test) in a for pointy block is very practical here.

sub greater-char ($target, @input) {
    return @input.grep({ $_ gt $target }).min;
}
for ('b', <e m u g>), ('a', <d c e f>), ('o', <j a r>),
    ('a', <d c a f>), ('v', <t g a l>)  ->  ($k, @test) {
    say "$k, (@test[]) \t -> ", greater-char($k, @test);
}

This program displays the following output:

$ raku ./greater-char.raku
b, (e m u g)     -> e
a, (d c e f)     -> c
o, (j a r)       -> r
a, (d c a f)     -> c
v, (t g a l)     -> Nil

Greater Character in Perl

The greater_char subroutine uses grep to build the list of characters lexicographically greater than the target value and returns the smallest one. Here, we lazily use sort to find the smallest character of the list; this is not the best algorithmic method (and it might not be good for very long lists of characters), but it is the fastest to develop. Saving development time is sometimes better than saving a few CPU cycles.

In the event that the character lists were significantly longer, we could use a min subroutine such as this one developed for a previous Perl Weekly Challenge task:

sub min {
    my $min = shift;
    for (@_) {
        $min = $_ if $_ lt $min;
    }
    return $min;
}

But this is not really needed here, so we use the built-in sort function.

use strict;
use warnings;
use feature qw/say/;

sub greater_char  {
    my @eligible_input = grep { $_ gt $_[0]} @{$_[1]};
    return (sort @eligible_input)[0];
}

for my $test (['b', [<e m u g>]], ['a', [<d c e f>]], 
    ['o', [<j a r>]], ['a', [<d c a f>]], ['v', [<t g a l>]]) {
    say "$test->[0] (@{$test->[1]}) \t -> ", greater_char($test->[0], $test->[1]);
}

This program displays the following output:

$ perl greater-char.pl
b (e m u g)      -> e
a (d c e f)      -> c
o (j a r)        -> r
a (d c a f)      -> c
v (t g a l)      ->

Task 2: Array Degree

You are given an array of 2 or more non-negative integers.

Write a script to find out the smallest slice, i.e. contiguous subarray of the original array, having the degree of the given array.

> The degree of an array is the maximum frequency of an element in the array.

Example 1

Input: @array = (1, 3, 3, 2)
Output: (3, 3)

The degree of the given array is 2.
The possible subarrays having the degree 2 are as below:
(3, 3)
(1, 3, 3)
(3, 3, 2)
(1, 3, 3, 2)

And the smallest of all is (3, 3).

Example 2

Input: @array = (1, 2, 1, 3)
Output: (1, 2, 1)

Example 3

Input: @array = (1, 3, 2, 1, 2)
Output: (2, 1, 2)

Example 4

Input: @array = (1, 1, 2, 3, 2)
Output: (1, 1)

Example 5

Input: @array = (2, 1, 2, 1, 1)
Output: (1, 2, 1, 1)

The definition of the degree of an array doesn’t state what the degree should be when more than one element reaches the maximum frequency. In my implementations, I have changed examples 3 and 4 of the task specification to avoid the problem.

Array Degree in Raku

The get-degree subroutine builds an %histogram hash of input integers with their frequency, and returns the histogram key having the highest value. Then, the main part of the program uses twice the built-in first routine to find the first and last occurrences of the computed degree in the input list.

sub get-degree (@input) {
    my %histogram;
    %histogram{$_}++ for @input;
    return (%histogram.max({$_.value})).key;
}

for <1 3 3 2>, <1 2 1 3>, <4 3 2 1 2> ,
    <1 1 2 3 4>, <2 1 2 1 1> -> @test {
    my $degree = get-degree(@test);
    my $start = @test.first: * == $degree, :k;
    my $end =   @test.first: * == $degree, :k :end;
    say "@test[] \t => @test[$start..$end]";
}

This program displays the following output:

$ raku ./array-degree.raku
1 3 3 2          => 3 3
1 2 1 3          => 1 2 1
4 3 2 1 2        => 2 1 2
1 1 2 3 4        => 1 1
2 1 2 1 1        => 1 2 1 1

Array Degree in Perl

The get_degree subroutine builds an histogram of input integers with their frequency, and returns the histogram key having the highest value. Note that, here again, we lazily use the built-in sort function to find the largest value. Please see my comment in the Greater Character in Perl section above about algorithmically better solutions. In the main part of the program, we just loop once over the list to find the first and last occurrences of the degree in the input list.

use strict;
use warnings;
use feature qw/say/;

sub get_degree {
    my %histo;  # histogram
    $histo{$_}++ for @_;
    return (sort { $histo{$b} <=> $histo{$a} } keys %histo)[0]
}

for my $test ([<1 3 3 2>], [<1 2 1 3>], 
    [<4 3 2 1 2>], [<1 1 2 3 4>], [<2 1 2 1 1>]) {
    my @list = @$test;
    my $degree = get_degree @list;
    my ($start, $end);
    for my $i (0..$#list) {
        if ($list[$i] == $degree) {
            $start = $i unless defined $start;
            $end = $i;
        }
    }
    say "@list \t => @list[$start..$end]";
}

This program displays the following output:

$ perl ./array-degree.pl
1 3 3 2          => 3 3
1 2 1 3          => 1 2 1
4 3 2 1 2        => 2 1 2
1 1 2 3 4        => 1 1
2 1 2 1 1        => 1 2 1 1

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on November 13, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.