TWC 193: Evens and Oddballs
In which we revisit seventh grade, and sing in the key of "A".
TWC Task #1 - Binary String
Task
Calculate all possible binary numbers of size $n.
Observations
In 7th grade, I noticed that you can construct all the binary numbers by starting with 0 and 1, then prepending 0-and-1 to all the prior numbers, doubling the size of the calculated each time:
0
1
00
01
10
11
000
001
010
011
100
101
110
111
I used this method in the Perl solution.
Loosely related: This week, my 5-year-old granddaughter
showed us how she can count to 2_000_000
:
Cora Kate: "one million, two million !!!"
Raku
I could have written:
sub task1 ( UInt $n ) { ^(2**$n) .fmt("\%0{$n}b") }
, but that gives the wrong answer when $n
is 0
. So instead:
multi sub task1 ( 1 ) { <0 1> }
multi sub task1 ( UInt $n ) { [X~] (<0 1> xx $n) }
The multi
is needed because the X
cross-operator will operate on a solo <0 1>
by unpacking it, translating to '0' ~ '1'
, which is not what we want.
Perl
I could have written:
sub task1 ( $n ) { map { sprintf "%0${n}b", $_ } 0 .. (2**$n)-1 }
, but that gives the wrong answer when $n
is 0
. (Sounds familiar.) So instead:
sub task1 ($n) {
my @r = ("");
@r = ( map("0$_", @r),
map("1$_", @r) ) for 1..$n;
return @r;
}
TWC Task #2 - Odd String
Task
All string elements of a given list will have the same "distance between adjacent characters", except one. Return that one string element.
Observations
This task is naturally broken into:
- Calculating the distance
- Grouping based on that distance
- Selecting the group with only one element
The solution will probably be clearer if we keep those aspects separate.
Changing "be"
into 1,4
to get the difference of 3
can be replaced with ord
, since the base values don't matter, only their relative distances.
Raku
I could have golfed this to:
@L.classify(~*.comb.rotor(2=>-1).map({[-] $_».ord})).values.min(+*)
, but T'is the season of thankfulness and giving. Also, I am not happy with the assumptions that the task is making, so I structured it to better allow for warnings.
sub oddballs ( @list, &matcher ) {
my %h = @list
.classify(&matcher)
.values
.classify({ .elems == 1 ?? 'Oddball' !! 'Clique' });
warn "Multiple cliques exist" if %h<Clique>.elems > 1;
return %h<Oddball>.list;
}
sub neighbor_distances ( Str $s --> Str ) {
return $s.comb
.map(&ord)
.rotor(2 => -1)
.map({ .[1] - .[0] })
.Str;
}
sub task2 (@list) {
die "Must have at least 3 to have an oddball" if @list.elems < 3;
my @o = oddballs( @list, &neighbor_distances );
warn "No oddballs found" if not @o;
warn "More than one oddball" if @o.elems > 1;
return @o.head;
}
I want to point out that the "neighbor-difference after numeric translation" is a fine way to specify the desired grouping, but an alternative could also serve:
$s .= trans( ['b'..'z'] => ['a'..'y'] ) until $s.contains: 'a';
Reducing each letter along the alphabet until any of them is an a
would produce the same results.
Perl
Using many modules, to better parallel the Raku solution:
use v5.36;
use List::Util qw<mesh pairvalues>;
use List::MoreUtils qw<slide>;
use List::Categorize qw<categorize>;
sub diffs ( $s ) {
state %A_N = mesh ['a'..'z'], [0..25];
return join ':',
slide { $b - $a }
@A_N{ split '', $s };
}
sub oddballs ( @s ) {
return grep { @{$_} == 1 }
pairvalues
categorize { diffs($_) } @s;
}
sub task2 ( @s ) {
my @r = oddballs(@s);
warn if @r != 1;
return $r[0][0];
}
mesh
is Raku'sZ
.slide
is Raku's.rotor(2 => -1)
.categorize
is Raku's.classify
.pairvalues
makes up for Raku's Pairs being true objects.
I used the @
-sigil on a hash to access Perl's "hash slicing", allowing all of the letters to be translated in one go.
Leave a comment