aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubos Kolouch <lubos@kolouch.net>2025-10-27 09:49:55 +0100
committerLubos Kolouch <lubos@kolouch.net>2025-10-27 09:49:55 +0100
commit3a0dbfc3827aab88997c27f909f319ec6527fa61 (patch)
tree2cd54631a2e98f5e8fc9abcf424ba390470f8f62
parent9b1eba54d5e59d05df44f336120a6a03be62a645 (diff)
downloadperlweeklychallenge-club-3a0dbfc3827aab88997c27f909f319ec6527fa61.tar.gz
perlweeklychallenge-club-3a0dbfc3827aab88997c27f909f319ec6527fa61.tar.bz2
perlweeklychallenge-club-3a0dbfc3827aab88997c27f909f319ec6527fa61.zip
Add lubos-kolouch solutions for challenge 345
-rw-r--r--challenge-345/lubos-kolouch/README53
-rw-r--r--challenge-345/lubos-kolouch/perl/ch-1.pl54
-rw-r--r--challenge-345/lubos-kolouch/perl/ch-2.pl73
-rw-r--r--challenge-345/lubos-kolouch/python/ch-1.py62
-rw-r--r--challenge-345/lubos-kolouch/python/ch-2.py58
5 files changed, 271 insertions, 29 deletions
diff --git a/challenge-345/lubos-kolouch/README b/challenge-345/lubos-kolouch/README
index 972fc9e0d2..5b34a3ed40 100644
--- a/challenge-345/lubos-kolouch/README
+++ b/challenge-345/lubos-kolouch/README
@@ -1,38 +1,33 @@
-# Perl Weekly Challenge Solutions
+# Perl Weekly Challenge 345 Solutions
-Solutions for Perl Weekly Challenge 344 presented in both Perl and Python.
+Perl and Python implementations for both tasks of Weekly Challenge 345.
-## Task 1: Array Form Compute
+## Task 1: Peak Positions
-### Description
-Given an integer represented as an array of digits (`@ints`) and another integer (`$x`), add them and return the resulting integer in array form.
+Identify every index whose value is strictly greater than its immediate
+neighbour(s). Endpoints are considered peaks when they exceed their sole
+neighbour.
-### Solution
-- **Perl (`perl/ch-1.pl`)**: Processes digits from least significant to most significant, maintaining a carry and constructing the resulting digit list.
-- **Python (`python/ch-1.py`)**: Mirrors the Perl approach using an iterative carry-based loop to build the new digit array.
-- **Examples Covered**:
- - `(1, 2, 3, 4) + 12` → `(1, 2, 4, 6)`
- - `(2, 7, 4) + 181` → `(4, 5, 5)`
- - `(9, 9, 9) + 1` → `(1, 0, 0, 0)`
- - `(1, 0, 0, 0, 0) + 9999` → `(1, 9, 9, 9, 9)`
- - `(0) + 1000` → `(1, 0, 0, 0)`
+- **Perl (`perl/ch-1.pl`)**: Validates the integer list with `Type::Params`,
+ then performs a single pass comparing each value against the surrounding
+ entries to collect the peak indices.
+- **Python (`python/ch-1.py`)**: Mirrors the linear scan with explicit type
+ hints and a set of `unittest` cases for the provided examples.
-## Task 2: Array Formation
+## Task 2: Last Visitor
-### Description
-Determine if the target array (`@target`) can be formed by concatenating subarrays from `@source` without altering the order inside each subarray, though subarrays themselves may be reordered.
+Process a mixed list of positive integers and `-1` markers. Each integer is
+stored at the front of a queue, while every `-1` requests the value `x` steps
+back in that queue, where `x` counts consecutive `-1` entries.
-### Solution
-- **Perl (`perl/ch-2.pl`)**: Iterates through the target, greedily matching unused source subarrays based on their leading element and length, marking each subarray as consumed once used.
-- **Python (`python/ch-2.py`)**: Utilises the same greedy matching strategy, leveraging Python's iteration constructs for clarity.
-- **Examples Covered**:
- - `([2,3], [1], [4])` → `(1, 2, 3, 4)` → `true`
- - `([1,3], [2,4])` → `(1, 2, 3, 4)` → `false`
- - `([9,1], [5,8], [2])` → `(5, 8, 2, 9, 1)` → `true`
- - `([1], [3])` → `(1, 2, 3)` → `false`
- - `([7,4,6])` → `(7, 4, 6)` → `true`
+- **Perl (`perl/ch-2.pl`)**: Maintains the visitor queue with input validation
+ and tracks the current `-1` run to select the right lookup or `-1` when the
+ queue is too short.
+- **Python (`python/ch-2.py`)**: Provides the same behaviour with typed
+ functions and unit tests aligned with the specification examples.
## Running the Solutions
-- **Perl**: Execute `perl perl/ch-1.pl` or `perl perl/ch-2.pl`.
-- **Python**: Execute `python3 python/ch-1.py` or `python3 python/ch-2.py`.
-- Each script includes embedded unit tests covering all examples from the task specification.
+
+- **Perl**: `perl perl/ch-1.pl` or `perl perl/ch-2.pl`
+- **Python**: `python3 python/ch-1.py` or `python3 python/ch-2.py`
+- Each script runs only the official example cases as unit tests.
diff --git a/challenge-345/lubos-kolouch/perl/ch-1.pl b/challenge-345/lubos-kolouch/perl/ch-1.pl
new file mode 100644
index 0000000000..0ba4d952a0
--- /dev/null
+++ b/challenge-345/lubos-kolouch/perl/ch-1.pl
@@ -0,0 +1,54 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use feature qw(signatures state);
+no warnings qw(experimental::signatures);
+use Type::Params qw(compile);
+use Types::Standard qw(ArrayRef Int);
+use Test::More;
+
+=pod
+
+=head1 PURPOSE
+
+Identify every peak in the supplied integer array. A peak is any element
+that is strictly greater than its immediate neighbours. The first and last
+elements are peaks if they are strictly greater than their single neighbour.
+
+=head1 ALGORITHM
+
+We validate the input with L<Type::Params> to ensure an array reference of
+integers. Iterating once over the array, we compare each element with its
+left and right neighbours where they exist. Indices meeting the peak
+predicate are collected and returned as an array reference.
+
+=cut
+
+## no critic (Subroutines::ProhibitSubroutinePrototypes)
+sub peak_positions ($ints_ref) {
+ state $check = compile( ArrayRef [Int] );
+ my ($ints) = $check->($ints_ref);
+
+ my @peaks;
+ for my $idx ( 0 .. $#$ints ) {
+ my $current = $ints->[$idx];
+ my $left = $idx > 0 ? $ints->[ $idx - 1 ] : undef;
+ my $right = $idx < $#$ints ? $ints->[ $idx + 1 ] : undef;
+
+ next if defined($left) && $current <= $left;
+ next if defined($right) && $current <= $right;
+
+ push @peaks, $idx;
+ }
+
+ return \@peaks;
+}
+
+# Example tests provided by the specification.
+is_deeply( peak_positions( [ 1, 3, 2 ] ), [1], 'Example 1' );
+is_deeply( peak_positions( [ 2, 4, 6, 5, 3 ] ), [2], 'Example 2' );
+is_deeply( peak_positions( [ 1, 2, 3, 2, 4, 1 ] ), [ 2, 4 ], 'Example 3' );
+is_deeply( peak_positions( [ 5, 3, 1 ] ), [0], 'Example 4' );
+is_deeply( peak_positions( [ 1, 5, 1, 5, 1, 5, 1 ] ), [ 1, 3, 5 ], 'Example 5' );
+
+done_testing();
diff --git a/challenge-345/lubos-kolouch/perl/ch-2.pl b/challenge-345/lubos-kolouch/perl/ch-2.pl
new file mode 100644
index 0000000000..f1d90f714c
--- /dev/null
+++ b/challenge-345/lubos-kolouch/perl/ch-2.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use feature qw(signatures state);
+no warnings qw(experimental::signatures);
+use Type::Params qw(compile);
+use Types::Standard qw(ArrayRef Int);
+use Test::More;
+
+=pod
+
+=head1 PURPOSE
+
+Simulate the “last visitor” queue defined in the challenge. Positive
+integers arriving from left to right are stored with the newest at the front,
+and each C<-1> requests the visitor seen C<x> places earlier in that queue,
+where C<x> counts consecutive C<-1> values encountered so far.
+
+=head1 ALGORITHM
+
+The input array is validated as an array reference of integers. We iterate
+through the values maintaining two arrays:
+
+=over 4
+
+=item *
+C<@seen> keeps previously encountered positive integers with the newest at
+the front.
+
+=item *
+C<@ans> collects the values to return for each C<-1>.
+
+=back
+
+Whenever we see a C<-1>, we look up the C<x>-th element in C<@seen> counting
+from zero (if present) and append that value to C<@ans>; otherwise we append
+C<-1>. Runs of C<-1> values are tracked so the lookup offset increments
+within the run, and resets when a positive integer appears.
+
+=cut
+
+## no critic (Subroutines::ProhibitSubroutinePrototypes)
+sub last_visitor ($ints_ref) {
+ state $check = compile( ArrayRef [Int] );
+ my ($ints) = $check->($ints_ref);
+
+ my @seen;
+ my @answer;
+ my $neg_run = 0;
+
+ for my $value (@$ints) {
+ if ( $value == -1 ) {
+ my $lookup = $neg_run < @seen ? $seen[$neg_run] : -1;
+ push @answer, $lookup;
+ $neg_run++;
+ next;
+ }
+
+ unshift @seen, $value;
+ $neg_run = 0;
+ }
+
+ return \@answer;
+}
+
+# Example tests provided by the specification.
+is_deeply( last_visitor( [ 5, -1, -1 ] ), [ 5, -1 ], 'Example 1' );
+is_deeply( last_visitor( [ 3, 7, -1, -1, -1 ] ), [ 7, 3, -1 ], 'Example 2' );
+is_deeply( last_visitor( [ 2, -1, 4, -1, -1 ] ), [ 2, 4, 2 ], 'Example 3' );
+is_deeply( last_visitor( [ 10, 20, -1, 30, -1, -1 ] ), [ 20, 30, 20 ], 'Example 4' );
+is_deeply( last_visitor( [ -1, -1, 5, -1 ] ), [ -1, -1, 5 ], 'Example 5' );
+
+done_testing();
diff --git a/challenge-345/lubos-kolouch/python/ch-1.py b/challenge-345/lubos-kolouch/python/ch-1.py
new file mode 100644
index 0000000000..352b18406c
--- /dev/null
+++ b/challenge-345/lubos-kolouch/python/ch-1.py
@@ -0,0 +1,62 @@
+"""Peak Positions Challenge Solution.
+
+This module implements the Weekly Challenge task that asks for all peak
+indices in a sequence of integers. Peaks are elements strictly greater than
+neighbours, with the endpoints compared against their single neighbour.
+"""
+
+from collections.abc import Iterable, Sequence
+import unittest
+
+
+def peak_positions(ints: Sequence[int]) -> list[int]:
+ """Return the zero-based indices of every peak in ``ints``.
+
+ Args:
+ ints: Sequence of integers to analyse.
+
+ Returns:
+ A list of indices at which the element is strictly greater than its
+ immediate neighbour(s).
+ """
+ peaks: list[int] = []
+ last_index = len(ints) - 1
+
+ for idx, value in enumerate(ints):
+ left = ints[idx - 1] if idx > 0 else None
+ right = ints[idx + 1] if idx < last_index else None
+
+ if left is not None and value <= left:
+ continue
+ if right is not None and value <= right:
+ continue
+ peaks.append(idx)
+
+ return peaks
+
+
+class PeakPositionsTests(unittest.TestCase):
+ """Unit tests restricted to the specification examples."""
+
+ def _assert_example(self, data: Iterable[int],
+ expected: list[int]) -> None:
+ self.assertEqual(peak_positions(list(data)), expected)
+
+ def test_example_1(self) -> None:
+ self._assert_example((1, 3, 2), [1])
+
+ def test_example_2(self) -> None:
+ self._assert_example((2, 4, 6, 5, 3), [2])
+
+ def test_example_3(self) -> None:
+ self._assert_example((1, 2, 3, 2, 4, 1), [2, 4])
+
+ def test_example_4(self) -> None:
+ self._assert_example((5, 3, 1), [0])
+
+ def test_example_5(self) -> None:
+ self._assert_example((1, 5, 1, 5, 1, 5, 1), [1, 3, 5])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/challenge-345/lubos-kolouch/python/ch-2.py b/challenge-345/lubos-kolouch/python/ch-2.py
new file mode 100644
index 0000000000..50877e33a6
--- /dev/null
+++ b/challenge-345/lubos-kolouch/python/ch-2.py
@@ -0,0 +1,58 @@
+"""Last Visitor Challenge Solution.
+
+Process a mixed sequence of positive integers and ``-1`` sentinel values to
+report, for each ``-1``, which previously seen visitor should be returned.
+"""
+
+from collections.abc import Iterable, Sequence
+import unittest
+
+
+def last_visitor(ints: Sequence[int]) -> list[int]:
+ """Return the lookups requested by ``-1`` entries in ``ints``.
+
+ Positive integers are stored with the newest at index 0. For a run of
+ ``-1`` values, the ``x``-th ``-1`` retrieves the ``x``-th element of the
+ stored list (if present) or ``-1`` otherwise.
+ """
+ seen: list[int] = []
+ answers: list[int] = []
+ neg_run = 0
+
+ for value in ints:
+ if value == -1:
+ answers.append(seen[neg_run] if neg_run < len(seen) else -1)
+ neg_run += 1
+ continue
+
+ seen.insert(0, value)
+ neg_run = 0
+
+ return answers
+
+
+class LastVisitorTests(unittest.TestCase):
+ """Unit tests restricted to the specification examples."""
+
+ def _assert_example(self, data: Iterable[int],
+ expected: list[int]) -> None:
+ self.assertEqual(last_visitor(list(data)), expected)
+
+ def test_example_1(self) -> None:
+ self._assert_example((5, -1, -1), [5, -1])
+
+ def test_example_2(self) -> None:
+ self._assert_example((3, 7, -1, -1, -1), [7, 3, -1])
+
+ def test_example_3(self) -> None:
+ self._assert_example((2, -1, 4, -1, -1), [2, 4, 2])
+
+ def test_example_4(self) -> None:
+ self._assert_example((10, 20, -1, 30, -1, -1), [20, 30, 20])
+
+ def test_example_5(self) -> None:
+ self._assert_example((-1, -1, 5, -1), [-1, -1, 5])
+
+
+if __name__ == "__main__":
+ unittest.main()