aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Muth <matthias.muth@gmx.de>2025-05-09 21:50:37 +0200
committerMatthias Muth <matthias.muth@gmx.de>2025-05-09 21:50:37 +0200
commit50c48267764e47ab045105f6da350d1b233d2bd4 (patch)
treec30757b6ca1fff771bd60b8851d7b80a914fa1dd
parent4260922ac702246fa5e6743be89f77846ce8d96f (diff)
downloadperlweeklychallenge-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.md251
-rw-r--r--challenge-320/matthias-muth/blog.txt1
-rwxr-xr-xchallenge-320/matthias-muth/perl/ch-1.pl56
-rwxr-xr-xchallenge-320/matthias-muth/perl/ch-2.pl52
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 ) },
+};
+
+