Perl Weekly Challenge 294.
My solutions (task 1 and task 2 ) to the The Weekly Challenge - 294.
Task 1: Consecutive Sequence
Submitted by: Mohammad Sajid Anwar
You are given an unsorted array of integers, @ints.
Write a script to return the length of the longest consecutive elements sequence.
Return -1 if none found. The algorithm must runs in O(n) time.
Example 1
Input: @ints = (10, 4, 20, 1, 3, 2)
Output: 4
The longest consecutive sequence (1, 2, 3, 4).
The length of the sequence is 4.
Example 2
Input: @ints = (0, 6, 1, 8, 5, 2, 4, 3, 0, 7)
Output: 9
Example 3
Input: @ints = (10, 30, 20)
Output: -1
I can map each number in the array to the bounds of a the consecutive interval to which it belongs while saving the longest. As I traverse the array I update the bounds. The bounds of the inner elements are not used so they don’t have to be updated when a new element is added. It is only neccesary that the boundary themselves be correct. This yields a two-liner.
Example 1:
perl -MList::Util=max -E '
sub d($x){defined $x}$w=0;for(@ARGV){$n=$b{$_+1};$p=$b{$_-1};$l=d($p)?$p->[0]:$_;$u=d($n)?$n->[1]:
$_;$b{$l}[0]=$b{$u}[0]=$l;$b{$l}[1]=$b{$u}[1]=$u;$w=max $w,$u-$l}say "@ARGV -> ", $w?$w+1:-1
' 10 4 20 1 3 2
Results:
10 4 20 1 3 2 -> 4
Example 2:
perl -MList::Util=max -E '
sub d($x){defined $x}$w=0;for(@ARGV){$n=$b{$_+1};$p=$b{$_-1};$l=d($p)?$p->[0]:$_;$u=d($n)?$n->[1]:
$_;$b{$l}[0]=$b{$u}[0]=$l;$b{$l}[1]=$b{$u}[1]=$u;$w=max $w,$u-$l}say "@ARGV -> ", $w?$w+1:-1
' 0 6 1 8 5 2 4 3 0 7
Results:
0 6 1 8 5 2 4 3 0 7 -> 9
Example 3:
perl -MList::Util=max -E '
sub d($x){defined $x}$w=0;for(@ARGV){$n=$b{$_+1};$p=$b{$_-1};$l=d($p)?$p->[0]:$_;$u=d($n)?$n->[1]:
$_;$b{$l}[0]=$b{$u}[0]=$l;$b{$l}[1]=$b{$u}[1]=$u;$w=max $w,$u-$l}say "@ARGV -> ", $w?$w+1:-1
' 10 30 20
Results:
10 30 20 -> -1
I would have prefered to answer 1 in this case, rather than -1, as there are three (degenerate) contiguous sequences of length 1, but I did follow the problem statement.
The full code is:
1 # Perl weekly challenge 294
2 # Task 1: Consecutive Sequence
3 #
4 # See https://wlmb.github.io/2024/11/04/PWC294/#task-1-consecutive-sequence
5 use v5.36;
6 use List::Util qw(max);
7 die <<~"FIN" unless @ARGV;
8 Usage: $0 N1 N2...
9 to find the length of the longest consecutive sequence
10 that may be built from the (possibly disordered) numbers
11 N1, N2...
12 FIN
13 my $length = 0;
14 my %bounds;
15 for(@ARGV){
16 my $previous = $bounds{$_-1};
17 my $next = $bounds{$_+1};
18 my $lower = defined($previous)? $previous->[0] : $_;
19 my $upper = defined($next)? $next->[1] : $_;
20 $bounds{$lower}[0] = $bounds{$upper}[0] = $lower;
21 $bounds{$lower}[1] = $bounds{$upper}[1] = $upper;
22 $length = max $length, $upper-$lower
23 }
24 my $result = $length? $length+1 : -1;
25 say "@ARGV -> ", $result;
Examples:
./ch-1.pl 10 4 20 1 3 2
./ch-1.pl 0 6 1 8 5 2 4 3 0 7
./ch-1.pl 10 30 20
Results:
10 4 20 1 3 2 -> 4
0 6 1 8 5 2 4 3 0 7 -> 9
10 30 20 -> -1
Task 2: Next Permutation
Submitted by: Mohammad Sajid Anwar
You are given an array of integers, @ints.
Write a script to find out the next permutation of the given array.
The next permutation of an array of integers is the next lexicographically
greater permutation of its integer.
Example 1
Input: @ints = (1, 2, 3)
Output: (1, 3, 2)
Permutations of (1, 2, 3) arranged lexicographically:
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
Example 2
Input: @ints = (2, 1, 3)
Output: (2, 3, 1)
Example 3
Input: @ints = (3, 1, 2)
Output: (3, 2, 1)
A simple, brute force, inefficient method would be to generate all permutations in
order until we find the current one and return the next. To that
purpose, we may use the permutations
iterator from the
Algorithm::Combinatorics
package, and use PDL
to compare
arrays. This yields a 1.5 liner.
Example 1:
perl -MAlgorithm::Combinatorics=permutations -MPDL -E '
$i=permutations([sort {$a <=> $b}@ARGV]); $o=pdl(@ARGV);1 while(any(pdl($i->next->@*)!=$o));
say "$o -> ", pdl($i->next->@*);
' 1 2 3
Results:
[1 2 3] -> [1 3 2]
Example 2:
perl -MAlgorithm::Combinatorics=permutations -MPDL -E '
$i=permutations([sort {$a <=> $b}@ARGV]); $o=pdl(@ARGV);1 while(any(pdl($i->next->@*)!=$o));
say "$o -> ", pdl($i->next->@*);
' 2 1 3
Results:
[2 1 3] -> [2 3 1]
Example 3:
perl -MAlgorithm::Combinatorics=permutations -MPDL -E '
$i=permutations([sort {$a <=> $b}@ARGV]); $o=pdl(@ARGV);1 while(any(pdl($i->next->@*)!=$o));
say "$o -> ", pdl($i->next->@*);
' 3 1 2
Results:
[3 1 2] -> [3 2 1]
For the full code I follow hints from section 4.3.1 of the book Higher Order Perl by Mark Jason Dominus. I first identify the actual permutation given, i.e., the places of the numbers I should remove from the ordered sequence to produce the given permutation, and then, increment them to get the next permutation.
1 # Perl weekly challenge 294
2 # Task 2: Next Permutation
3 #
4 # See https://wlmb.github.io/2024/11/04/PWC294/#task-2-next-permutation
5 use v5.36;
6 use List::AllUtils qw(first_index);
7 die <<~"FIN" unless @ARGV;
8 Usage: $0 N1 N2...
9 to find the next permutation of the numbers N1 N2...
10 in lexicographic order.
11 FIN
12 sub pattern($permutation, $original){
13 my @permutation=@$permutation;
14 my @original=@$original;
15 my @result;
16 for my $n(@permutation){
17 my $first=first_index {$n == $_} @original;
18 splice @original, $first, 1;
19 push @result, $first;
20 }
21 return [@result];
22 }
23 sub next_pattern($permutation){
24 my @permutation=@$permutation;
25 my @next=reverse @permutation;
26 for(0..@next-1){
27 $next[$_]++;
28 last unless $next[$_]>$_;
29 $next[$_]=0;
30 }
31 return [reverse @next];
32 }
33 sub pattern_to_permutation($pattern, $original){
34 my @pattern=@$pattern;
35 my @original=@$original;
36 my @result;
37 for(@pattern){
38 push @result, splice @original, $_, 1;
39 }
40 return [@result];
41 }
42
43 my @permutation=@ARGV;
44 my @sorted=sort {$a <=> $b} @permutation;
45 my $pattern=pattern([@permutation],[@sorted]);
46 my $next_pattern=next_pattern($pattern);
47 my $next_permutation=pattern_to_permutation($next_pattern, [@sorted]);
48 say "@permutation -> @$next_permutation";
Example 1:
./ch-2.pl 1 2 3
Results:
1 2 3 -> 1 3 2
The sequence (1 2 3) corresponds to the ordered sequence (1 2 3) (the same) and is obtained by removing the zeroth term (1), then the zeroth term of the ordered remaining numbers (2 3) and finally the zeroth term of the remaining number (3), corresponding to the pattern 0 0 0. We increment it starting from the right, taking into account that there is only one choice for the last index, two choices for the next to last, three choices for the next to next to last, etc., obtaining the pattern 0 1 0, so we take the zeroth element (1) from the array (1 2 3), the element 1 (3) from the remaining array (2 3) and finally, the element 0 (2) from the array (2), yielding the pattern (1 3 2).
Example 2:
./ch-2.pl 2 1 3
Results:
2 1 3 -> 2 3 1
In this case, the pattern corresponding to the permutation (2 1 3) is (1 0 0), which is incremented to (1 1 0), which corresponds to the permutation (2 3 1).
Example 3:
./ch-2.pl 3 1 2
Results:
3 1 2 -> 3 2 1
I compare the performance of the full code to that of the one liner above:
(time perl -MAlgorithm::Combinatorics=permutations -MPDL -E '
$i=permutations([sort {$a <=> $b}@ARGV]); $o=pdl(@ARGV);1 while(any(pdl($i->next->@*)!=$o));
say "$o -> ", pdl($i->next->@*);
' 10 9 8 7 6 5 4 3 1 2 ) 2>&1
(time ./ch-2.pl 10 9 8 7 6 5 4 3 1 2) 2>&1
Results:
[10 9 8 7 6 5 4 3 1 2] -> [10 9 8 7 6 5 4 3 2 1]
real 1m3.098s
user 1m3.073s
sys 0m0.016s
10 9 8 7 6 5 4 3 1 2 -> 10 9 8 7 6 5 4 3 2 1
real 0m0.017s
user 0m0.012s
sys 0m0.004s
So my first solution becomes ridiculously inefficient as the size of the array has a modest increase.
Another difference between the one liner and the full code is the tratment of the last permutation:
perl -MAlgorithm::Combinatorics=permutations -MPDL -E '
$i=permutations([sort {$a <=> $b}@ARGV]); $o=pdl(@ARGV);1 while(any(pdl($i->next->@*)!=$o));
say "$o -> ", pdl($i->next->@*);
' 3 2 1
./ch-2.pl 3 2 1
Results:
[3 2 1] -> 0
3 2 1 -> 1 2 3
The one liner didn’t find a next permutation and answered 0, while my full code started over again with the first permutation.
/;