aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--challenge-277/bruce-gray/blog.txt1
-rw-r--r--challenge-277/bruce-gray/perl/ch-1.pl43
-rw-r--r--challenge-277/bruce-gray/perl/ch-2.pl94
-rw-r--r--challenge-277/bruce-gray/raku/ch-1.raku16
-rw-r--r--challenge-277/bruce-gray/raku/ch-2.raku174
5 files changed, 328 insertions, 0 deletions
diff --git a/challenge-277/bruce-gray/blog.txt b/challenge-277/bruce-gray/blog.txt
new file mode 100644
index 0000000000..d52db3dd6c
--- /dev/null
+++ b/challenge-277/bruce-gray/blog.txt
@@ -0,0 +1 @@
+https://blogs.perl.org/users/bruce_gray/2024/07/twc-277-strength-uncombined.html \ No newline at end of file
diff --git a/challenge-277/bruce-gray/perl/ch-1.pl b/challenge-277/bruce-gray/perl/ch-1.pl
new file mode 100644
index 0000000000..48f3d2c4e3
--- /dev/null
+++ b/challenge-277/bruce-gray/perl/ch-1.pl
@@ -0,0 +1,43 @@
+use v5.40;
+use Set::Bag;
+
+package Set::Bag { # Extending the class with methods I wish were already included.
+ use List::Util qw<pairgrep>;
+ sub new_from_list ($self, $list_aref) {
+ my $bag = Set::Bag->new();
+ $bag->insert($_ => 1) for @{$list_aref};
+ return $bag;
+ }
+ sub singles ($self) {
+ return Set::Bag->new( pairgrep { $b == 1 } $self->grab );
+ }
+ sub count ($self) {
+ my @e = $self->elements();
+ return 0 + @e;
+ }
+}
+
+use List::Util qw<reduce>;
+sub task1 ( @LoLists ) {
+ my $intersections_bag =
+ reduce { $a & $b }
+ map { Set::Bag->new_from_list($_)->singles }
+ @LoLists;
+
+ return $intersections_bag->count;
+}
+
+
+my @tests = (
+ [ 2, [qw<Perl is my friend>], [qw<Perl and Raku are friend>] ],
+ [ 1, [qw<Perl and Python are very similar>], [qw<Python is top in guest languages>] ],
+ [ 0, [qw<Perl is imperative Lisp is functional>], [qw<Crystal is similar to Ruby>] ],
+
+ [ 1, [qw<Perl is Perl>], [qw<Java is not>] ],
+);
+use Test::More;
+plan tests => 0+@tests;
+for (@tests) {
+ my ($expected, $in1, $in2) = @{$_};
+ is task1($in1, $in2), $expected;
+}
diff --git a/challenge-277/bruce-gray/perl/ch-2.pl b/challenge-277/bruce-gray/perl/ch-2.pl
new file mode 100644
index 0000000000..c960017095
--- /dev/null
+++ b/challenge-277/bruce-gray/perl/ch-2.pl
@@ -0,0 +1,94 @@
+use v5.40;
+use List::Util qw<uniq min>;
+use ntheory qw<forcomb>;
+
+sub Triangle ($n) { $n * ($n-1) / 2 } # https://oeis.org/A000217
+sub Quarter_square ($n) { int( $n * $n / 4 ) } # https://oeis.org/A002620
+sub N42 ($n) { 4 ** $n - 2 ** $n } # https://oeis.org/A020522
+
+# From Raku: task2_combo_unique
+sub task2a ($unordered_nonunique_numbers_aref) {
+ my @ns = uniq @{$unordered_nonunique_numbers_aref};
+
+ my $r = 0;
+ forcomb {
+ my ( $x, $y ) = @ns[@_];
+ my $s = abs( $x - $y );
+ $r++ if 0 < $s and $s < min($x, $y);
+ } @ns, 2;
+
+ return $r;
+}
+
+# From Raku: task2_linear_after_sort_early_return
+sub task2b ($unordered_nonunique_numbers_aref) {
+ my @ns = sort { $a <=> $b }
+ uniq @{$unordered_nonunique_numbers_aref};
+
+ my $highest = $ns[-1];
+
+ my $y = 0;
+ my $r = 0;
+ for my $x ( keys @ns ) {
+ my $xv2 = $ns[$x] * 2;
+
+ if ($xv2 > $highest) {
+ $r += Triangle(+@ns - $x);
+ last;
+ }
+
+ $y++ while ($y+1) <= $#ns and $ns[$y+1] < $xv2;
+
+ $r += $y - $x;
+ }
+
+ return $r;
+}
+
+
+my @tests = (
+ [ 4, [1, 2, 3, 4, 5] ],
+ [ 1, [5, 7, 1, 7] ],
+
+ [ 3, [6, 7, 8] ],
+ [ 241, [ 2,2,2,2,2,2,2,2,2,2,4,3,4,4,3,4,4,4,3,3,5,6,7,7,8,8,5,5,5,5,16,14,15,10,12,16,13,13,14,10,22,24,27,23,21,31,27,25,23,28,42,53,60,58,57,58,33,39,62,38,88,115,108,79,80,90,101,79,127,96 ] ],
+ [ 467, [ 2,2,2,2,2,2,2,2,2,2,4,3,4,4,3,3,3,3,3,4,7,6,8,8,8,6,6,5,5,5,12,16,12,12,13,16,15,9,16,14,25,26,21,18,21,32,29,25,25,25,54,51,49,46,39,54,49,64,37,42,86,93,123,101,70,96,71,125,86,104,131,165,160,217,241,239,217,234,210,193,409,376,390,419,501,313,387,500,403,465,646,564,639,997,720,513,928,796,848,869 ] ],
+);
+
+my $EXTRA_TESTS = true;
+if ($EXTRA_TESTS) {
+
+ for my $i (1..100) {
+ my $Qi1 = Quarter_square($i-1);
+ push @tests, [ $Qi1 , [1..$i] ] if $i > 1+1;
+ push @tests, [ $Qi1 , [2..$i] ] if $i > 2+1;
+ push @tests, [ $Qi1 - 1, [3..$i] ] if $i > 3+1;
+ push @tests, [ $Qi1 - 3, [4..$i] ] if $i > 4+1;
+ push @tests, [ $Qi1 - 6, [5..$i] ] if $i > 5+1;
+ push @tests, [ $Qi1 - 10, [6..$i] ] if $i > 6+2;
+ push @tests, [ $Qi1 - 15, [7..$i] ] if $i > 7+3;
+ push @tests, [ $Qi1 - 21, [8..$i] ] if $i > 8+4;
+ }
+
+ for my $N (1..100) {
+ push @tests, [ Triangle($N) , [$N .. (2*$N)-1] ];
+ }
+
+ for my $N (2..11) {
+ push @tests, [ N42($N) , [1 .. (2 ** ($N+1))] ];
+ }
+
+}
+my @subs = (
+ # Fastest to slowest
+ [ task2b => \&task2b ],
+ [ task2a => \&task2a ],
+);
+use Test::More; plan tests => @subs * @tests;
+for my $aref (@subs) {
+ my ($sub_name, $task2_subref) = @{$aref};
+ for (@tests) {
+ my ( $expected, $ns ) = @{$_};
+ is $task2_subref->($ns), $expected;
+ }
+}
diff --git a/challenge-277/bruce-gray/raku/ch-1.raku b/challenge-277/bruce-gray/raku/ch-1.raku
new file mode 100644
index 0000000000..fad5d35877
--- /dev/null
+++ b/challenge-277/bruce-gray/raku/ch-1.raku
@@ -0,0 +1,16 @@
+sub task1 ( @LoLists --> UInt ) {
+
+ return +[∩] map { .Set (-) .repeated }, @LoLists;
+}
+
+
+use Test; plan +my @tests =
+ ( 2, <Perl is my friend>, <Perl and Raku are friend> ),
+ ( 1, <Perl and Python are very similar>, <Python is top in guest languages> ),
+ ( 0, <Perl is imperative Lisp is functional>, <Crystal is similar to Ruby> ),
+
+ ( 1, <Perl is Perl>, <Java is not> ),
+;
+for @tests -> ( $expected, @in1, @in2 ) {
+ is task1([@in1, @in2]), $expected;
+}
diff --git a/challenge-277/bruce-gray/raku/ch-2.raku b/challenge-277/bruce-gray/raku/ch-2.raku
new file mode 100644
index 0000000000..f491104a29
--- /dev/null
+++ b/challenge-277/bruce-gray/raku/ch-2.raku
@@ -0,0 +1,174 @@
+# See blog post for detailed commentary.
+
+sub Triangle ( UInt $n --> UInt ) { $n * ($n-1) div 2 } # https://oeis.org/A000217 , [+]1..$n
+sub Quarter-square ( UInt $n --> UInt ) { $n * $n div 4 } # https://oeis.org/A002620
+sub N42 ( UInt $n --> UInt ) { 4 ** $n - 2 ** $n } # https://oeis.org/A020522
+
+sub task2_combo_unique ( @ns --> UInt ) {
+
+ sub is_strong_pair ( (\x,\y) --> Bool ) { 0 < abs(x - y) < min(x,y) }
+
+ return +grep &is_strong_pair, combinations(@ns.unique, 2);
+}
+sub task2_combo_sort ( @ns --> UInt ) {
+
+ return +grep ->(\x,\y){ (y - x) < x }, combinations( @ns.sort(+*).squish, 2 );
+}
+sub task2_linear_after_sort ( @ns --> UInt ) {
+ my @ns_ss = @ns.sort(+*).squish;
+
+ my ( $y, $r ) = 0 xx *;
+ for @ns_ss.kv -> $x, $xv {
+ my $x2 = $xv * 2;
+
+ $y++ while ($y+1) <= @ns_ss.end and @ns_ss[$y+1] < $x2;
+
+ $r += $y - $x;
+ }
+
+ return $r;
+}
+sub task2_linear_after_sort_early_return ( @ns --> UInt ) {
+ my @ns_ss = @ns.sort(+*).squish;
+
+ my $highest = @ns_ss.tail;
+
+ my ( $y, $r ) = 0 xx *;
+ for @ns_ss.kv -> $x, $xv {
+ my $x2 = $xv * 2;
+
+ if $x2 > $highest {
+ $r += Triangle(+@ns_ss - $x);
+ last;
+ }
+
+ $y++ while ($y+1) <= @ns_ss.end and @ns_ss[$y+1] < $x2;
+
+ $r += $y - $x;
+ }
+
+ return $r;
+}
+
+
+
+
+constant DO_TESTS = True;
+constant ADD_EXTRA_TESTS = True;
+constant DO_BENCHMARKS = False;
+my @tests =
+ ( 4, (1, 2, 3, 4, 5) ),
+ ( 1, (5, 7, 1, 7) ),
+
+ ( 241, ( 2,2,2,2,2,2,2,2,2,2,4,3,4,4,3,4,4,4,3,3,5,6,7,7,8,8,5,5,5,5,16,14,15,10,12,16,13,13,14,10,22,24,27,23,21,31,27,25,23,28,42,53,60,58,57,58,33,39,62,38,88,115,108,79,80,90,101,79,127,96 ) ),
+ ( 467, ( 2,2,2,2,2,2,2,2,2,2,4,3,4,4,3,3,3,3,3,4,7,6,8,8,8,6,6,5,5,5,12,16,12,12,13,16,15,9,16,14,25,26,21,18,21,32,29,25,25,25,54,51,49,46,39,54,49,64,37,42,86,93,123,101,70,96,71,125,86,104,131,165,160,217,241,239,217,234,210,193,409,376,390,419,501,313,387,500,403,465,646,564,639,997,720,513,928,796,848,869 ) ),
+
+ # 4ⁿ - 2ⁿ for ranges 1..2ⁿ⁺¹
+ ( 2, ( 1..4 ) ),
+ ( 12, ( 1..8 ) ),
+ ( 56, ( 1..16 ) ),
+ ( 240, ( 1..32 ) ),
+ ( 992, ( 1..64 ) ),
+
+ ( 0, ( 1..1 ) ),
+
+ ( 121-3, ( 4..23 ) ),
+
+ ( 121-6, ( 5..23 ) ),
+
+ ( 121-10, ( 6..23 ) ),
+
+ ( 3, (6, 7, 8)),
+;
+if ADD_EXTRA_TESTS {
+ # Contiguous range from a low number.
+ for 1..100 -> $i {
+ my $Qi1 = Quarter-square($i-1);
+ push @tests, ( $Qi1 , (1..$i) ) if $i > 1+1;
+ push @tests, ( $Qi1 , (2..$i) ) if $i > 2+1;
+ push @tests, ( $Qi1 - 1, (3..$i) ) if $i > 3+1;
+ push @tests, ( $Qi1 - 3, (4..$i) ) if $i > 4+1;
+ push @tests, ( $Qi1 - 6, (5..$i) ) if $i > 5+1;
+ push @tests, ( $Qi1 - 10, (6..$i) ) if $i > 6+2;
+ push @tests, ( $Qi1 - 15, (7..$i) ) if $i > 7+3;
+ push @tests, ( $Qi1 - 21, (8..$i) ) if $i > 8+4;
+ }
+
+ for 1..100 -> \N {
+ # Any range N ..^ 2N should result in the Triangle number for N.
+ push @tests, ( Triangle(N) , (N ..^ (2*N)) );
+ }
+
+ # Contiguous ranges 1..2ⁿ⁺¹ should result in 4ⁿ - 2ⁿ
+ for 2..8 -> \N { # At 9 or above the task2_combo_* subs are terrible.
+ push @tests, ( N42(N) , (1 .. (2 ** (N+1))) );
+ }
+
+}
+
+# Fastest to slowest
+my @subs =
+ :&task2_linear_after_sort_early_return,
+ :&task2_linear_after_sort,
+ :&task2_combo_sort,
+ :&task2_combo_unique,
+;
+if DO_TESTS {
+ use Test; plan +@tests * +@subs;
+ for @subs -> ( :key($sub_name), :value(&task2) ) {
+ for @tests -> ( $expected, $ns ) {
+ my $in_desc = $ns ~~ Range ?? $ns.raku !! $ns.head(6);
+
+ is task2($ns), $expected, "$sub_name : $in_desc";
+ }
+ }
+}
+
+# XXX Very messy past this point; only used for crude benchmarking
+# if DO_BENCHMARKS {
+# my $t = now;
+# for @subs -> ( :key($sub_name), :value(&task2) ) {
+# for ^10 { # 50
+# for @tests -> ( $expected, $ns ) {
+# task2($ns);
+# }
+# }
+# say now - $t, " : $sub_name";
+# $t = now;
+# }
+# }
+# if DO_BENCHMARKS {
+# # for 16, 32, 64 ... * -> $size {
+# for 1..* -> $power {
+# say (:$power, size => (2**$power) );
+# my $t = now;
+# for @subs -> ( :key($sub_name), :value(&task2) ) {
+# # for ^1 { # 50
+# # for @tests -> ( $expected, $ns ) {
+# task2(1 .. (2**$power));
+# # }
+# # }
+# say now - $t, " : $sub_name";
+# $t = now;
+# }
+# }
+# }
+
+if DO_BENCHMARKS {
+ # for 16, 32, 64 ... * -> $size {
+ for 1..* -> $power {
+ say (:$power, size => (2**$power), 'X4' );
+ my @in = ( 1 .. (2**$power) ) X* 4;
+ my $t = now;
+ for @subs -> ( :key($sub_name), :value(&task2) ) {
+ # for ^1 { # 50
+ # for @tests -> ( $expected, $ns ) {
+ # task2(1 .. (2**$power));
+ task2(@in);
+ # }
+ # }
+ say now - $t, " : $sub_name";
+ $t = now;
+ }
+ }
+}