diff options
| -rw-r--r-- | challenge-095/dave-jacoby/blog.txt | 1 | ||||
| -rw-r--r-- | challenge-095/dave-jacoby/perl/ch-1.pl | 34 | ||||
| -rw-r--r-- | challenge-095/dave-jacoby/perl/ch-2.pl | 85 | ||||
| -rw-r--r-- | challenge-096/dave-jacoby/blog.txt | 1 | ||||
| -rw-r--r-- | challenge-096/dave-jacoby/perl/ch-1.pl | 49 | ||||
| -rw-r--r-- | challenge-096/dave-jacoby/perl/ch-2.pl | 190 |
6 files changed, 360 insertions, 0 deletions
diff --git a/challenge-095/dave-jacoby/blog.txt b/challenge-095/dave-jacoby/blog.txt new file mode 100644 index 0000000000..b7f76f7f29 --- /dev/null +++ b/challenge-095/dave-jacoby/blog.txt @@ -0,0 +1 @@ +https://jacoby.github.io/2021/01/11/ada-bob-hannah-nin-perl-weekly-challenge-95.html
\ No newline at end of file diff --git a/challenge-095/dave-jacoby/perl/ch-1.pl b/challenge-095/dave-jacoby/perl/ch-1.pl new file mode 100644 index 0000000000..b6fe19801a --- /dev/null +++ b/challenge-095/dave-jacoby/perl/ch-1.pl @@ -0,0 +1,34 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use feature qw{ say signatures state }; +no warnings qw{ experimental }; + +use Scalar::Util qw{looks_like_number}; + +my @numbers = ( 1221, -101, 90, 2112, 9, 90.09 ); + +for my $num (@numbers) { + my $r = is_palindrome_number($num); + say qq{Input: $num}; + say qq{Output: $r}; + say ''; +} + +# this is specifically about numbers, so we'll use +# looks_like_number from Scalar::Util. Otherwise we'll +# assume base-10 and treat it like a decimal, which is +# how Perl likes to stringify numbers. + +# returns 0 if not a number +# returns 0 if not a palindrome +# what remains should only be palindromic numbers, +# so returns 1 + +sub is_palindrome_number($num = 0) { + return 0 unless looks_like_number($num); + my $mun = join '', reverse split //, $num; + return 0 unless $mun eq $num; + return 1; +} diff --git a/challenge-095/dave-jacoby/perl/ch-2.pl b/challenge-095/dave-jacoby/perl/ch-2.pl new file mode 100644 index 0000000000..62cab6323a --- /dev/null +++ b/challenge-095/dave-jacoby/perl/ch-2.pl @@ -0,0 +1,85 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use feature qw{ say signatures state }; +no warnings qw{ experimental }; + +# I use `say` instead of `print` because the newlines +# improve readability in the output + +my $stack = Stack->new; +$stack->push(2); +$stack->push(-1); +$stack->push(0); +$stack->all; # 2, -1, 0 + +$stack->pop; # removes 0 +$stack->all; + +say $stack->top; # prints -1 +$stack->push(0); + +$stack->all; +say $stack->min; # prints -1 + +say 'DONE'; +exit; + +# I think a Moose-style implementation would be good for me +package Stack; + +# see min, below. +use List::Util; + +# creates a new Stack object. Except for the "min" function, +# there's nothing to keep this from handling anything that a +# scalar can hold: number, string, hashref, arrayref, closure +sub newx ( $class ) { + my $self = {}; + $self->{values} = []; + return bless $self, $class; +} + +sub new ( $class, @values ) { + my $self = {}; + $self->{values} = []; + push $self->{values}->@*, @values; + return bless $self, $class; +} + +# 'push' and 'pop' are methods used by Perl's Array type +# to handle stack values, so here we just use them +sub push ( $self, $value ) { + push $self->{values}->@*, $value; + return 1; +} + +sub pop ( $self ) { + return pop $self->{values}->@*; +} + +# it's harder to reuse subroutines from libraries than +# it is to reuse those provided by Perl, so we have to +# use List::Util and use min's long name rather than +# use List::Util qw{min}, because Perl would think we're +# rewriting min. +sub min ( $self ) { + return List::Util::min( $self->{values}->@* ); +} + +# the thing I had to remember that push and pop occur from +# the back of the stack, not the front, and thus top is the +# _last_ value, not the _first_. I could use {values}[0] +# if instead I used shift and unshift. +sub top ( $self ) { + return $self->{values}[-1]; + return 1; +} + +# bookkeeping function so I know what's going on inside +sub all ( $self ) { + say join "\n\t", 'Size: ' . scalar $self->{values}->@*, + $self->{values}->@*, ''; + return 1; +} diff --git a/challenge-096/dave-jacoby/blog.txt b/challenge-096/dave-jacoby/blog.txt new file mode 100644 index 0000000000..7ec383b99e --- /dev/null +++ b/challenge-096/dave-jacoby/blog.txt @@ -0,0 +1 @@ +https://jacoby.github.io/2021/01/19/going-the-distance-perl-weekly-challenge-96.html diff --git a/challenge-096/dave-jacoby/perl/ch-1.pl b/challenge-096/dave-jacoby/perl/ch-1.pl new file mode 100644 index 0000000000..336009661b --- /dev/null +++ b/challenge-096/dave-jacoby/perl/ch-1.pl @@ -0,0 +1,49 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use feature qw{ say signatures state }; +no warnings qw{ experimental }; + +# I suppose I'm commenting instead of blogging, or pre-blogging, +# but the key questions with this challenge are: +# * "What is a word?" +# * "What is a word boundary?" + +# Mohammad gives us an easy one with "The Weekly Challenge", +# where we can easily split on spaces and reverse, but padding +# spaces at the front and end of the next example make things harder. +# I mean, we COULD just use a regular expression to pull out the +# begining and ending spaces, but splitting on /\s/ (space characters) +# and grepping for /\S/ (non-space characters would be OK) + +# but a space character isn't a word boundary. I asked and brian d foy +# gave me good advice about word boundaries. I mean, I asked the Internet +# and found his blog post: +# https://www.effectiveperlprogramming.com/2016/06/perl-v5-22-adds-fancy-unicode-word-boundaries/ + +# I threw in the line from Buffalo Springfield's "For What It's Worth" +# because it adds an apostrophe, which confounds the pre-5.22 default +# word border separation. + +my @inputs; +push @inputs, 'The Weekly Challenge'; +push @inputs, ' Perl and Raku are part of the same family '; +push @inputs, q{Nobody's right if everybody's wrong}; + +for my $s (@inputs) { + my $r = reverse_words($s); + say <<"END"; + Input: "$s" + Output: "$r" +END +} + +# This is a very pipe-y, very Dave way of doing this. Beyond the +# sub signature, I split on word boundaries as the blog suggests, +# grep to make sure that there's something non-space, revers and +# join with a space. I am sure a confounding string can be created +# but I'll engage that when the challenge is presented. +sub reverse_words ( $string ) { + return join ' ', reverse grep /\S/, split /\b{wb}/, $string; +} diff --git a/challenge-096/dave-jacoby/perl/ch-2.pl b/challenge-096/dave-jacoby/perl/ch-2.pl new file mode 100644 index 0000000000..8e113ffa1d --- /dev/null +++ b/challenge-096/dave-jacoby/perl/ch-2.pl @@ -0,0 +1,190 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use feature qw{ say signatures state }; +no warnings qw{ experimental }; + +use List::Util qw{min}; + +my @input; +push @input, [qw{ kitten sitting }]; +push @input, [qw{ sunday monday }]; +push @input, [qw{ slight lights }]; +push @input, [qw{ fed feed }]; +push @input, [qw{ test test }]; +push @input, [qw{ test text }]; + +for my $x (@input) { + edit_distance( $x->@* ); +} + +# I first found the Levenshtein distance when poking in the center of +# perlbrew. This is how it knows, when you type `perlbrew xeec` to +# suggest you try `perlbrew exec` instead. This gives us the first +# part, the number of changes you'd need to get from S1 to S2. I thought +# about but never implemented it as a kind-of 404 page for endpoints: +# you look like you're looking for "index" but typed "idnex", for example. + +# What we don't get from editdist is WHICH changes those would be. +# therefore, it's a half-solution for this problem. + +sub edit_distance ( $s1, $s2 ) { + editdist( $s1, $s2 ); +} + +# let's try to make this a whole solution +sub editdist ( $s1, $s2 ) { + my @s1 = split //, $s1; + my @s2 = split //, $s2; + my @d; + $d[$_][0] = $_ for ( 0 .. @s1 ); + $d[0][$_] = $_ for ( 0 .. @s2 ); + + # this creates a two-dimensional array that starts like this: + # [0,1,2,3,4,5,6] + # [1, , , , , , ] + # [2, , , , , , ] + # [3, , , , , , ] + # [4, , , , , , ] + # [5, , , , , , ] + # which gets filled in iteratively in the nested loops below + + for my $i ( 1 .. @s1 ) { + for my $j ( 1 .. @s2 ) { + + # Let's understand this. For a particular i and j position + # if the two agree, D[i][j] equals D[-i][-j] + + # if they don't however, we find the value above + # the value before and the one above and before, find + # the lowest, and add one. + + # this means that $d[-1][-1] would have the total + + $d[$i][$j] = ( + $s1[ $i - 1 ] eq $s2[ $j - 1 ] + ? $d[ $i - 1 ][ $j - 1 ] + : 1 + min( + $d[ $i - 1 ][$j], + $d[$i][ $j - 1 ], + $d[ $i - 1 ][ $j - 1 ] + ) + + ); + } + } + + print <<"END"; + + Input: S1: $s1 + S2: $s2 + Change Count: $d[-1][-1] +END + + my @operations = find_changes( \@d, \@s1, \@s2 ); + + my $c = 1; + for my $operation ( reverse @operations ) { + say qq{ Operation $c: $operation}; + $c++; + } + + # returns the last column of the last row, which SHOULD + # be the min changes. + return $d[-1][-1]; +} + +# d = 2-dimensional array, result of LD +# s1 = array of first input +# s2 = array of second input +# i = current row +# j = current column + +sub find_changes ( $d, $s1, $s2, $i = -1, $j = -1 ) { + + # -1 means implicit end of array, which gets turned + # into explicit end of array + $i = $i == -1 ? -1 + scalar $d->@* : $i; + $j = $j == -1 ? -1 + scalar $d->[-1]->@* : $j; + + # zero means that there are no more changes + return if $d->[$i][$j] == 0; + + my $v = $d->[$i][$j]; + my $v1 = $d->[ $i - 1 ][ $j - 1 ]; + my $v2 = $d->[$i][ $j - 1 ]; + my $v3 = $d->[ $i - 1 ][$j]; + + my @output; + if (0) { + + # The impossible situation we never planned for. + # I LIKE to put an if ( false ) statement first, + # so it's easy to just move the elsifs around + # should I decide or discern that I have the order + # wrong. + } + elsif ( $v1 == $v - 1 ) { + my $c1 = $s1->[ $i - 1 ]; + my $c2 = $s2->[ $j - 1 ]; + push @output, qq{replace '$c1' with '$c2'}; + push @output, find_changes( $d, $s1, $s2, $i - 1, $j - 1 ); + } + elsif ( $v2 == $v - 1 ) { + my $c1 = $s1->[ $i - 1 ]; + my $c2 = $s2->[ $j - 1 ]; + if ( $j == scalar $s2->@* ) { + push @output, qq{insert '$c2' at the end}; + } + else { push @output, qq{insert '$c2'}; } + push @output, find_changes( $d, $s1, $s2, $i, $j - 1 ); + } + elsif ( $v3 == $v - 1 ) { + my $c1 = $s1->[ $i - 1 ]; + my $c2 = $s2->[ $j - 1 ]; + push @output, qq{remove '$c1' from beginning} if $i == 1; + push @output, qq{remove '$c1'} if $i != 1; + push @output, find_changes( $d, $s1, $s2, $i - 1, $j ); + } + elsif ( $v1 == $v ) { + push @output, find_changes( $d, $s1, $s2, $i - 1, $j - 1 ); + } + elsif ( $v2 == $v ) { + push @output, find_changes( $d, $s1, $s2, $i, $j - 1 ); + } + elsif ( $v3 == $v ) { + push @output, find_changes( $d, $s1, $s2, $i - 1, $j ); + } + return @output; +} + +# ------------------------------------------------------------------- +# straight copy of Wikipedia's "Levenshtein Distance" +sub levenshtein_distance { + my ( $f, $g ) = @_; + my @a = split //, $f; + my @b = split //, $g; + + # There is an extra row and column in the matrix. This is the + # distance from the empty string to a substring of the target. + my @d; + $d[$_][0] = $_ for ( 0 .. @a ); + $d[0][$_] = $_ for ( 0 .. @b ); + + for my $i ( 1 .. @a ) { + for my $j ( 1 .. @b ) { + $d[$i][$j] = ( + $a[ $i - 1 ] eq $b[ $j - 1 ] + ? $d[ $i - 1 ][ $j - 1 ] + : 1 + min( + $d[ $i - 1 ][$j], + $d[$i][ $j - 1 ], + $d[ $i - 1 ][ $j - 1 ] + ) + ); + } + } + return $d[@a][@b]; +} + |
