diff options
| author | Lubos Kolouch <lubos@kolouch.net> | 2025-10-27 09:49:55 +0100 |
|---|---|---|
| committer | Lubos Kolouch <lubos@kolouch.net> | 2025-10-27 09:49:55 +0100 |
| commit | 3a0dbfc3827aab88997c27f909f319ec6527fa61 (patch) | |
| tree | 2cd54631a2e98f5e8fc9abcf424ba390470f8f62 | |
| parent | 9b1eba54d5e59d05df44f336120a6a03be62a645 (diff) | |
| download | perlweeklychallenge-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/README | 53 | ||||
| -rw-r--r-- | challenge-345/lubos-kolouch/perl/ch-1.pl | 54 | ||||
| -rw-r--r-- | challenge-345/lubos-kolouch/perl/ch-2.pl | 73 | ||||
| -rw-r--r-- | challenge-345/lubos-kolouch/python/ch-1.py | 62 | ||||
| -rw-r--r-- | challenge-345/lubos-kolouch/python/ch-2.py | 58 |
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() |
