diff options
| author | Matthias Muth <matthias.muth@gmx.de> | 2025-05-09 21:50:37 +0200 |
|---|---|---|
| committer | Matthias Muth <matthias.muth@gmx.de> | 2025-05-09 21:50:37 +0200 |
| commit | 50c48267764e47ab045105f6da350d1b233d2bd4 (patch) | |
| tree | c30757b6ca1fff771bd60b8851d7b80a914fa1dd | |
| parent | 4260922ac702246fa5e6743be89f77846ce8d96f (diff) | |
| download | perlweeklychallenge-club-50c48267764e47ab045105f6da350d1b233d2bd4.tar.gz perlweeklychallenge-club-50c48267764e47ab045105f6da350d1b233d2bd4.tar.bz2 perlweeklychallenge-club-50c48267764e47ab045105f6da350d1b233d2bd4.zip | |
Challenge 320 Task 1 and 2 solutions in Perl by Matthias Muth
| -rw-r--r-- | challenge-320/matthias-muth/README.md | 251 | ||||
| -rw-r--r-- | challenge-320/matthias-muth/blog.txt | 1 | ||||
| -rwxr-xr-x | challenge-320/matthias-muth/perl/ch-1.pl | 56 | ||||
| -rwxr-xr-x | challenge-320/matthias-muth/perl/ch-2.pl | 52 |
4 files changed, 258 insertions, 102 deletions
diff --git a/challenge-320/matthias-muth/README.md b/challenge-320/matthias-muth/README.md index 5103802a85..9953f56187 100644 --- a/challenge-320/matthias-muth/README.md +++ b/challenge-320/matthias-muth/README.md @@ -1,174 +1,221 @@ -# Does 'Weekly' Have a Happy (Vowel) Ending? +# Elegance Makes the Maximum Difference +**Challenge 320 solutions in Perl by Matthias Muth** -**Challenge 319 solutions in Perl by Matthias Muth** +## Task 1: Maximum Count -## Task 1: Word Count - -> You are given a list of words containing alphabetic characters only.<br/> -> Write a script to return the count of words either starting with a vowel or ending with a vowel. +> You are given an array of integers.<br/> +> Write a script to return the maximum between the number of positive and negative integers. Zero is neither positive nor negative. > > **Example 1** > > ```text -> Input: @list = ("unicode", "xml", "raku", "perl") -> Output: 2 -> -> The words are "unicode" and "raku". -> ``` -> -> **Example 2** +> Input: @ints = (-3, -2, -1, 1, 2, 3) +> Output: 3 > -> ```text -> Input: @list = ("the", "weekly", "challenge") +> There are 3 positive integers. +> There are 3 negative integers. +> The maximum between 3 and 3 is 3. +>``` +> +>**Example 2** +> +>```text +> Input: @ints = (-2, -1, 0, 0, 1) > Output: 2 +> +>There are 1 positive integers. +> There are 2 negative integers. +> The maximum between 2 and 1 is 2. > ``` > > **Example 3** > > ```text -> Input: @list = ("perl", "python", "postgres") -> Output: 0 +>Input: @ints = (1, 2, 3, 4) +> Output: 4 +> +> There are 4 positive integers. +>There are 0 negative integers. +> The maximum between 4 and 0 is 4. > ``` -What is a vowel, really?<br/> -Doesn't 'weekly' happily end in a vowel?<br/> -Don't worry (oh, another one!). <br/> -I'm a programmer, so I'm infallible! -And as I have grown up with ASCII, so there can only be one meaning of what a vowel really is.<br/>Here it is. These are <u>The Vowels</u>: +It's a matter of taste: -``` - a e i o u -``` +* <u>Single pass</u>:<br/> + Go through the list of integers in a loop, + increment one of two counters depending on the number's sign. + Then return the maximum of the two counts. -So please, no [Sunday sermons](https://en.wikipedia.org/wiki/Vowel) about vowels actually being sounds, not letters...<br/>And no discussions about [vowels in Unicode](https://stackoverflow.com/questions/38792789/how-to-match-unicode-vowels)! (Hint: The concept of a vowel does not even exist in Unicode!) +* <u>Two-pass</u>:<br/> + Count the negative and positive numbers separately, + then return the maximum. -So first let's put out a clear statement to declare what vowels are. Once and for all, and no matter which case: +Clearly, from an algorithm point of view, and regarding performance, +the single pass alternative is to be preferred. +No waste of resources going through the data twice +only to ignore half of the data in each pass. +So let's write it out: ```perl - my $vowel = qr/[aeiou]/i; +# Single pass solution. +sub maximum_count( @ints ) { + my ( $count_pos, $count_neg ) = ( 0, 0 ); + for ( @ints ) { + if ( $_ > 0 ) { + ++$count_pos + } + elsif ( $_ < 0 ) { + ++$count_neg + } + } + return $count_pos > $count_neg ? $count_pos : $count_neg; +} ``` -Ok. Feels better now. - -The rest is easy! Let's use some readable regular expression to match 'words either starting with a vowel or ending with a vowel': - -```perl - / ^ $vowel | $vowel $ /xi -``` +But even if this probably is high-performance, it has a problem:<br/> +I don't really like it.<br/> +Too much programming! -That's easy, isn't it? +For a two-pass solution, with a little bit of Perl magic and a functional touch, +we can avoid the `for` loop, +and we can avoid using variables at all. -And now we will let `grep` do the counting (it does so in scalar context), and we are done: +For me, this is a much clearer and nicer solution: ```perl use v5.36; -sub word_count( @list ) { - my $vowel = qr/[aeiou]/i; - return scalar grep / ^ $vowel | $vowel $ /xi, @list; +use List::Util qw( max ); + +# Two-pass, preferred solution. +sub maximum_count( @ints ) { + return max( scalar grep( $_ > 0, @ints ), scalar grep( $_ < 0, @ints ) ); } ``` -Makes me happi! +I will reconsider only once I really need that one little bit of higher performance.<br/> +Until then, I am very happy with less programming and less typos! +## Task 2: Sum Difference - -## Task 2: Minimum Common - -> You are given two arrays of integers.<br/> -> Write a script to return the minimum integer common to both arrays. If none found return -1. +> You are given an array of positive integers.<br/> +> Write a script to return the absolute difference between digit sum and element sum of the given array. > > **Example 1** > > ```text -> Input: @array_1 = (1, 2, 3, 4) -> @array_2 = (3, 4, 5, 6) -> Output: 3 +> Input: @ints = (1, 23, 4, 5) +> Output: 18 > -> The common integer in both arrays: 3, 4 -> The minimum is 3. +> Element sum: 1 + 23 + 4 + 5 => 33 +> Digit sum: 1 + 2 + 3 + 4 + 5 => 15 +> Absolute difference: | 33 - 15 | => 18 >``` > >**Example 2** > >```text -> Input: @array_1 = (1, 2, 3) -> @array_2 = (2, 4) -> Output: 2 +> Input: @ints = (1, 2, 3, 4, 5) +> Output: 0 +> +>Element sum: 1 + 2 + 3 + 4 + 5 => 15 +> Digit sum: 1 + 2 + 3 + 4 + 5 => 15 +> Absolute difference: | 15 - 15 | => 0 > ``` > > **Example 3** > > ```text ->Input: @array_1 = (1, 2, 3, 4) -> @array_2 = (5, 6, 7, 8) -> Output: -1 -> ``` - -I first tried to find something like a 'symmetric' solution. Kind of combining the two arrays, and then see if I could derive the result from there. I didn't come up with anything clever. +>Input: @ints = (1, 2, 34) +> Output: 27 +> +> Element sum: 1 + 2 + 34 => 37 +>Digit sum: 1 + 2 + 3 + 4 => 10 +> Absolute difference: | 37 - 10 | => 27 +> ``` -Then, my idea for an 'asymmetric' solution was to walk through one array (that's one side), and check for existence of the element in the other (that's the second side). +We need to compare two sums: -As the number also has to be the smallest possible one, I first sort it numerically. Then we can return `true` for the first element that also exists in the second array. +* the sum of the integers in `@ints`, +* the digit sum of the integers in `@ints`. -Checking for existence is best done with an 'existence hash': +The first part is very simple. Using `sum` from `List::Util`: ```perl - my %is_in_array_2 = map { ( $_ => 1 ) } $array_2->@*; + sum( @ints ) ``` -The sorting and checking fits in one statement, avoiding a `for` loop, when we use `first` from `List::Util`, and then use the 'defined or' operator to return the special value `-1` if we don't find any entry that matches the condition. +For the second part, the digit sum, we have several options: -That can look like this: +* Map each integer in `@ints` into its <u>individual digit sum</u>, + then sum up those digit sums: -```perl -# First solution, using 'sort'. -use v5.36; -use List::Util qw( first ); -sub minimum_common_with_sorting( $array_1, $array_2 ) { - my %is_in_array_2 = map { ( $_ => 1 ) } $array_2->@*; - return - ( first { $is_in_array_2{$_} } - sort { $a <=> $b } $array_1->@* ) - // -1; -} -``` + ```perl + sum( map sum( split "", $_ ), @ints ) + ``` -I implemented this solution first, and it then it kept on thinking about how I could avoid the `sort`, and the multiple passes needed for `sort` and `first`. +* Map each integer in `@ints` into the <u>list of its digits</u>, + then sum up all the digits in one go: -Of course. It's so easy!<br/> -Finding the smallest number in a list of numbers: -That's the reason why the `min` function exists! + ```perl + sum( map split( "", $_ ), @ints ) + ``` -So I flip things around, *first* walking through the first array *without* sorting, -but filtering out only those elements that also exist in the second array. This is the same as finding the *intersection* of the two sets of numbers (notwithstanding repeated elements). + This avoids the repeated calls to `sum` for the individual digit sums. -```perl - grep $is_in_array_2{$_}, $array_1->@* -``` +* Concatenate all the integers in `@ints` into <u>one string</u>, + then split it up into single digits, + and sum them up: -Then find the (hooray!) minimum of those filtered common values: + ```perl + sum( split "", join "", @ints ) + ``` -```perl - min( grep $is_in_array_2{$_}, $array_1->@* ) -``` -Again, if there is no such element, I use the 'defined or' operator to return `-1`. +So here we are again: +There's more than one way to do it...<br/> +And choosing my favorite is not easy here. + +I would probably not use the first one.<br/> +It simply isn't necessary to have every individual element's digit sum. +We can easily avoid all those function calls. + +The second one for me is the 'correct' one. <br/> +Nothing done that is not necessary, very efficient. -Not only that `min` (also from `List::Util`) has a lower time complexity than `sort`, -but the solution also is shorter.<br/> -And I think it is easier to understand, too: +But my heart beats for the third solution. + +Even if it needs that additional step of generating an intermediate string, +I like how concise and simple it is. +And apart from the additional memory needed for that string +I'm not even sure whether it's not just as efficient as the 'correct' second solution.<br/> +Only a benchmark could tell. + +So here we go.<br/> +If in doubt, I choose elegance! ```perl -# My preferred solution. use v5.36; -use List::Util qw( min ); -sub minimum_common( $array_1, $array_2 ) { - my %is_in_array_2 = map { ( $_ => 1 ) } $array_2->@*; - return min( grep $is_in_array_2{$_}, $array_1->@* ) // -1; + +use List::Util qw( sum ); + +sub sum_difference( @ints ) { + return abs( sum( split "", join "", @ints ) - sum( @ints ) ); } ``` -Let's call it 'evolutionari programming'.<br/> -That's OK for me! +**Addendum:** + +I couldn't refrain myself from running that benchmark: + +```text + Rate sum_difference_1 sum_difference_2 sum_difference_3 +sum_difference_1 136455/s -- -12% -24% +sum_difference_2 155121/s 14% -- -14% +sum_difference_3 180004/s 32% 16% -- +``` + +My preferred one-string solution is the fastest!<br/> +What can I say? + #### **Thank you for the challenge!** diff --git a/challenge-320/matthias-muth/blog.txt b/challenge-320/matthias-muth/blog.txt new file mode 100644 index 0000000000..33a2a6220f --- /dev/null +++ b/challenge-320/matthias-muth/blog.txt @@ -0,0 +1 @@ +https://github.com/MatthiasMuth/perlweeklychallenge-club/tree/muthm-320/challenge-320/matthias-muth#readme diff --git a/challenge-320/matthias-muth/perl/ch-1.pl b/challenge-320/matthias-muth/perl/ch-1.pl new file mode 100755 index 0000000000..ae7f2634fd --- /dev/null +++ b/challenge-320/matthias-muth/perl/ch-1.pl @@ -0,0 +1,56 @@ +#!/usr/bin/env perl +# +# The Weekly Challenge - Perl & Raku +# (https://theweeklychallenge.org) +# +# Challenge 320 Task 1: Maximum Count +# +# Perl solution by Matthias Muth. +# + +use v5.36; + +sub maximum_count_1( @ints ) { + my ( $count_pos, $count_neg ) = ( 0, 0 ); + for ( @ints ) { + if ( $_ > 0 ) { + ++$count_pos + } + elsif ( $_ < 0 ) { + ++$count_neg + } + } + return $count_pos > $count_neg ? $count_pos : $count_neg; +} + +use List::Util qw( max ); + +sub maximum_count_2( @ints ) { + return max( scalar grep( $_ > 0, @ints ), scalar grep( $_ < 0, @ints ) ); +} + +*maximum_count = \&maximum_count_2; + +use Test2::V0 qw( -no_srand ); + +is maximum_count( -3, -2, -1, 1, 2, 3 ), 3, + 'Example 1: maximum_count( -3, -2, -1, 1, 2, 3 ) == 3'; +is maximum_count( -2, -1, 0, 0, 1 ), 2, + 'Example 2: maximum_count( -2, -1, 0, 0, 1 ) == 2'; +is maximum_count( 1, 2, 3, 4 ), 4, + 'Example 3: maximum_count( 1, 2, 3, 4 ) == 4'; + +done_testing; + +__END__ + +use Benchmark qw( :all ); + +my @ints = ( -3, -2, -1, 1, 2, 3 ); + +cmpthese -3 => { + maximum_count_1 => sub { maximum_count_1( @ints ) }, + maximum_count_2 => sub { maximum_count_2( @ints ) }, +}; + + diff --git a/challenge-320/matthias-muth/perl/ch-2.pl b/challenge-320/matthias-muth/perl/ch-2.pl new file mode 100755 index 0000000000..eeac1dfa70 --- /dev/null +++ b/challenge-320/matthias-muth/perl/ch-2.pl @@ -0,0 +1,52 @@ +#!/usr/bin/env perl +# +# The Weekly Challenge - Perl & Raku +# (https://theweeklychallenge.org) +# +# Challenge 320 Task 2: Sum Difference +# +# Perl solution by Matthias Muth. +# + +use v5.36; + +use List::Util qw( sum ); + +sub sum_difference_1( @ints ) { + return abs( sum( map sum( split "", $_ ), @ints ) - sum( @ints ) ); +} + +sub sum_difference_2( @ints ) { + return abs( sum( map split( "", $_ ), @ints ) - sum( @ints ) ); +} + +sub sum_difference_3( @ints ) { + return abs( sum( split "", join "", @ints ) - sum( @ints ) ); +} + +*sum_difference = \&sum_difference_3; + +use Test2::V0 qw( -no_srand ); + +is sum_difference( 1, 23, 4, 5 ), 18, + 'Example 1: sum_difference( 1, 23, 4, 5 ) == 18'; +is sum_difference( 1, 2, 3, 4, 5 ), 0, + 'Example 2: sum_difference( 1, 2, 3, 4, 5 ) == 0'; +is sum_difference( 1, 2, 34 ), 27, + 'Example 3: sum_difference( 1, 2, 34 ) == 27'; + +done_testing; + +__END__ + +use Benchmark qw( :all ); + +my @ints = ( 1, 23, 4, 5 ); + +cmpthese -3 => { + sum_difference_1 => sub { sum_difference_1( @ints ) }, + sum_difference_2 => sub { sum_difference_2( @ints ) }, + sum_difference_3 => sub { sum_difference_3( @ints ) }, +}; + + |
