Perl Weekly Challenge 145.
My solutions (task 1 and task 2 ) to the The Weekly Challenge - 145.
Task 1: Dot Product
Submitted by: Mohammad S Anwar
You are given 2 arrays of same size, @a and @b.
Write a script to implement Dot Product.
Example:
@a = (1, 2, 3);
@b = (4, 5, 6);
$dot_product = (1 * 4) + (2 * 5) + (3 * 6) => 4 + 10 + 18 =>
32
This one is really simple using PDL, as it has an inner
method. I assume the inputs are in @ARGV as strings with space
separated numbers, so I just make ndarrays
from them and ask
PDL to do the processing:
perl -MPDL -E '($x,$y)=map pdl([split " ", $_]), @ARGV; say "x=$x, y=$y, x.y=",
$x->inner($y)' "1 2 3" "4 5 6"
Results:
x=[1 2 3], y=[4 5 6], x.y=32
Otherwise, a solution could be built applying a reduction over the pairs of numbers, as in
perl -MList::MoreUtils=pairwise -MList::Util=sum0 -E '
($x,$y)=map [split " ", $_], @ARGV; $r=sum0 pairwise {$a*$b} @$x, @$y;
say "x=[",join(",", @$x),"], y=[", join(",", @$y),"], x.y=$r"' "1 2 3" "4 5 6"
Results:
x=[1,2,3], y=[4,5,6], x.y=32
The expanded solution is
1 # Perl weekly challenge 145
2 # Task 1: Dot product
3 #
4 # See https://wlmb.github.io/2021/12/27/PWC145/#task-1-dot-product
5 use v5.12;
6 use warnings;
7 use PDL;
8 use List::Util;
9 die "Usage: ./ch-1.pl 'x1 x2... xn' 'y1 y2... yn' to find dot product x.y"
10 unless @ARGV==2;
11 my ($x,$y)=map pdl([split " ", $_]), @ARGV;
12 die "Vectors should have the same positive length"
13 unless $x->nelem>0 && $y->nelem>0 && $x->nelem==$y->nelem;
14 say "x=$x, y=$y, x.y=", $x->inner($y);
Example:
./ch-1.pl "1 2 3" "4 5 6"
Results:
x=[1 2 3], y=[4 5 6], x.y=32
Task 2: Palindromic Tree
Submitted by: Mohammad S Anwar
You are given a string $s.
Write a script to create a Palindromic Tree for the given
string.
I found this [[https://medium.com/@alessiopiergiacomi/eertree-or-palindromic-tree-82453e75025b][blog]] exaplaining Palindromic Tree in detail.
Example 1:
Input: $s = 'redivider'
Output: r redivider e edivide d divid i ivi v
Example 2:
Input: $s = 'deific'
Output: d e i ifi f c
Example 3:
Input: $s = 'rotors'
Output: r rotor o oto t s
Example 4:
Input: $s = 'challenge'
Output: c h a l ll e n g
Example 5:
Input: $s = 'champion'
Output: c h a m p i o n
Example 6:
Input: $s = 'christmas'
Output: c h r i s t m a
I found several explanations, such as 1, 2, 3,
and 4, about palindrome trees and how to construct them. There
is one step I coudln’t figure out from their explanations (how
to get the suffix
of a given node, see below) but
after thinking for some time, experimenting, and getting some sleep, I finally
found out. I should have used proper objects in the following
program, but the problem was simple enought that I used a
straightforward explicit hash representation for the tree
nodes.1
To each palindromic fragment within the given string there corresponds a
node (I use an anonimous hash for its representation)
with three entries, a size
corresponding to the number of
characters of the fragment (0 for the null string and -1 for a
special imaginary node), a reference suffix
to its largest
palindromic suffix and a hash edges
of references indexed by
single letters pointing to larger palindromes built by
appending and prepending the corresponding letters. Thus if
there is a node $p
corresponding to a palindromic fragment P
and the
string contains a fragment Q=xPx
where x
is a single
letter, then the node $p
for P
would have an edge
$p->{edges}->{x}
with value $q
, the node corresponding to
Q
. Furthermore, $q->{size}
would equal
$p->{size}+2
. Finally, $q->{suffix}
is obtained by
following the suffix chain of $p
until a palindrome R
is
found, corresponding to a node $r
such that xRx
is
in the original string. As $r->{edge}->{x}
corresponds
precisely to the palindrome xRx
, which is the largest
palindromic suffix of Q
, then $q->{suffix}
should
be set to $r->{edge}->{x}
. This is guaranteed to exist, as
xRx
is also a palindromic prefix and must have been seen
before. There is a special treatment for the imaginary root
I
of size -1, for which xIx
is taken to mean x
by itself
(of length -1+2=1).
1 # Perl weekly challenge 145
2 # Task 2: Palindromic tree
3 #
4 # See https://wlmb.github.io/2021/12/27/PWC145/#task-1-palindromic-tree
5 use v5.12;
6 use warnings;
7 use Text::Wrap qw(wrap $columns $break);
8
9 $columns=62;
10 $break=qr/\s/;
11
12 die "Usage: ./ch-2.pl string to find palindrome substrings" unless @ARGV;
13 my ($imaginary_root, $root, $current);
14 my @letters;
15 foreach(@ARGV){
16 $imaginary_root={size=>-1, edges=>{}};
17 $root={size=>0, edges=>{}};
18 $current=$root;
19 $_->{suffix}=$imaginary_root foreach($root, $imaginary_root);
20 @letters=grep {!/\s/} split '', lc $_; #ignore spaces and case
21 foreach(0..@letters-1){
22 add_letter($_);
23 }
24 my $output=[()];
25 palindromes($imaginary_root->{edges}->{$_}, $_, $output)
26 foreach keys $imaginary_root->{edges}->%*;
27 palindromes($root, "", $output);
28 say "Input: $_\nOutput: ", wrap("", " ",
29 join ", ", sort {length $a <=> length $b or $a cmp $b} $output->@*);
30 }
31 sub add_letter {
32 my $index=shift;
33 my $letter=$letters[$index];
34 $current=$current->{suffix}
35 while $index-$current->{size}-1<0
36 || $letters[$index-$current->{size}-1] ne $letter;
37 $current=$current->{edges}{$letter}, return
38 if defined $current->{edges}{$letter};
39 my $suffix=$current->{suffix};
40 $current=$current->{edges}->{$letter}
41 ={size=>$current->{size}+2, edges=>{}};
42 $current->{suffix}=$root, return if $current->{size}==1;
43 $suffix=$suffix->{suffix}
44 while $letters[$index-$suffix->{size}-1] ne $letter;
45 $current->{suffix}=$suffix->{edges}->{$letter};
46 return;
47 }
48
49 sub palindromes {
50 my ($start, $center, $output)=@_;
51 push @$output, $center if $center; # ignore the blank string of $root
52 palindromes($start->{edges}->{$_}, $_.$center.$_, $output)
53 foreach keys $start->{edges}->%*;
54 }
The implementation of the algorithm starts with a $root
of zero length and an
$imaginary_root
of length -1, with no edges and both pointing
to the $imaginary_root
(lines 16-19). The last added node is kept in
$current
and initialized to the $root
(line 18). I
ignore spaces and case in
order to analyse palindromic phrases (line20). I
add_letter
’s to the structure one by
one (line 22). In line 34 I follow the suffix chain of the current node
until I find a string from which to build a new palindromic
fragment present in the original string. If the fragment had
already been found, I update the
current fragment and return (line 37). Otherwise I setup a new node and
update the $current->{edges}
and the new $current
(line 40). Then I look for the proper
suffix
for the new node starting from the previous
$current->{suffix}
(line 39). Line 42 deals with the trivial
case. In line 43 we follow the suffix
chain until we find an
appropriate one, which could be the $imaginary_root
. The
sought after suffix
for the new node is then obtained by
following the edge
corresponding to the single
letter we are looking at (line 45).
To print the list of all palindromic fragments we follow the edges of
the roots recursively (line 52); the
odd-sized fragments starting from the $imaginary_root
(line 25) and the even-sized starting from the $root
. When we
follow an edge
with a given label x
, we prepend and append
the label to the $center
string to get the
new fragment. Finally, I sort all fragments by size and
alphabetically before printing.
Examples:
./ch-2.pl redivider deific rotors challenge champion christmas
Results:
Input: redivider
Output: d, e, i, r, v, ivi, divid, edivide, redivider
Input: deific
Output: c, d, e, f, i, ifi
Input: rotors
Output: o, r, s, t, oto, rotor
Input: challenge
Output: a, c, e, g, h, l, n, ll
Input: champion
Output: a, c, h, i, m, n, o, p
Input: christmas
Output: a, c, h, i, m, r, s, t
Another example, a classical palindrome in Spanish,
./ch-2.pl "Anita lava la tina"
Results:
Input: Anita lava la tina
Output: a, i, l, n, t, v, ala, ava, laval, alavala, talavalat,
italavalati, nitalavalatin, anitalavalatina
In order to apply the program to real texts I would have to remove punctuation. For analyzing palindromes in Spanish I would have to remove some diacritical marks. Thus, I prepared a modified program.
1 # Perl weekly challenge 145
2 # Task 2: Palindromic tree. Removing punctuation and diacritical marks
3 #
4 # See https://wlmb.github.io/2021/12/27/PWC145/#task-1-palindromic-tree
5 use v5.12;
6 use warnings;
7 use utf8;
8 use Encode qw(decode_utf8);
9 use Text::Wrap qw(wrap $columns $break);
10 use Text::Unidecode;
11
12 $columns=62;
13 $break=qr/\s/;
14
15 die "Usage: ./ch-2a.pl string to find palindrome substrings" unless @ARGV;
16 my ($imaginary_root, $root, $current);
17 my @letters;
18 foreach(@ARGV){ # assume utf8 in @ARGV
19 my $s=decode_utf8($_,9);
20 $imaginary_root={size=>-1, edges=>{}};
21 $root={size=>0, edges=>{}};
22 $current=$root;
23 $_->{suffix}=$imaginary_root foreach($root, $imaginary_root);
24 $s=~s/\p{Punct}//g; #remove punctuation
25 $s=~s/\s*//g; #remove spaces
26 @letters=split '', unidecode(lc $s); #ignore case, remove accents
27 foreach(0..@letters-1){
28 add_letter($_);
29 }
30 my $output=[()];
31 palindromes($imaginary_root->{edges}->{$_}, $_, $output)
32 foreach keys $imaginary_root->{edges}->%*;
33 palindromes($root, "", $output);
34 say "Input: $_\nOutput: ", wrap("", " ",
35 join ", ", sort {length $a <=> length $b or $a cmp $b} $output->@*);
36 }
37 sub add_letter {
38 my $index=shift;
39 my $letter=$letters[$index];
40 $current=$current->{suffix}
41 while $index-$current->{size}-1<0
42 || $letters[$index-$current->{size}-1] ne $letter;
43 $current=$current->{edges}{$letter}, return
44 if defined $current->{edges}{$letter};
45 my $suffix=$current->{suffix};
46 $current=$current->{edges}->{$letter}
47 ={size=>$current->{size}+2, edges=>{}};
48 $current->{suffix}=$root, return if $current->{size}==1;
49 $suffix=$suffix->{suffix}
50 while $letters[$index-$suffix->{size}-1] ne $letter;
51 $current->{suffix}=$suffix->{edges}->{$letter};
52
53 return;
54 }
55
56 sub palindromes {
57 my ($start, $center, $output)=@_;
58 push @$output, $center if $center; # ignore the blank string of $root
59 palindromes($start->{edges}->{$_}, $_.$center.$_, $output)
60 foreach keys $start->{edges}->%*;
61 }
Example, from Merlyna Acevedo’s book Relojes de Arena:
./ch-2a.pl "Alba helada: la luz azul al alba heríale. El aire
habla, ¡la luz azul alada le habla!"
Results:
Input: Alba helada: la luz azul al alba heríale. El aire
habla, ¡la luz azul alada le habla!
Output: a, b, d, e, h, i, l, r, u, z, ee, ada, ala, lal, zaz, leel,
ladal, lalal, uzazu, aleela, luzazul, ialeelai,
aluzazula, rialeelair, laluzazulal, erialeelaire,
alaluzazulala, herialeelaireh, aherialeelaireha,
baherialeelairehab, lbaherialeelairehabl,
albaherialeelairehabla, lalbaherialeelairehablal,
alalbaherialeelairehablala,
lalalbaherialeelairehablalal,
ulalalbaherialeelairehablalalu,
zulalalbaherialeelairehablalaluz,
azulalalbaherialeelairehablalaluza,
zazulalalbaherialeelairehablalaluzaz,
uzazulalalbaherialeelairehablalaluzazu,
luzazulalalbaherialeelairehablalaluzazul,
aluzazulalalbaherialeelairehablalaluzazula,
laluzazulalalbaherialeelairehablalaluzazulal,
alaluzazulalalbaherialeelairehablalaluzazulala,
dalaluzazulalalbaherialeelairehablalaluzazulalad,
adalaluzazulalalbaherialeelairehablalaluzazulalada,
ladalaluzazulalalbaherialeelairehablalaluzazulaladal,
eladalaluzazulalalbaherialeelairehablalaluzazulaladal
e,
heladalaluzazulalalbaherialeelairehablalaluzazulalada
leh,
aheladalaluzazulalalbaherialeelairehablalaluzazulalad
aleha,
baheladalaluzazulalalbaherialeelairehablalaluzazulala
dalehab,
lbaheladalaluzazulalalbaherialeelairehablalaluzazulal
adalehabl,
albaheladalaluzazulalalbaherialeelairehablalaluzazula
ladalehabla
Finally, I try my luck with object oriented programming using (for the
first time) Object::Pad
to build my classes.
1 # Perl weekly challenge 145
2 # Task 2: Palindromic tree. Object based
3 #
4 # See https://wlmb.github.io/2021/12/27/PWC145/#task-1-palindromic-tree
5 use v5.26;
6 use warnings;
7 use utf8;
8 use Object::Pad;
9
10 class PNode {
11 has $size :param :reader;
12 has %edges;
13 has $suffix :param :accessor =undef;
14 method edge {
15 my $label=shift;
16 return $edges{$label};
17 }
18 method edges {
19 return keys %edges;
20 }
21 method add_edge {
22 my ($label, $target)=@_;
23 die "Duplicate edge" if defined $edges{$label};
24 $edges{$label}=$target;
25 }
26 BUILD {
27 %edges=();
28 }
29 };
30 class PTree {
31 use Encode qw(decode_utf8);
32 use Text::Unidecode;
33 has $string :param :reader;
34 has $imaginary_root :reader;
35 has $root :reader;
36 has $current :reader;
37
38 has @letters;
39 method $add_letter {
40 my $index=shift;
41 my $letter=$letters[$index];
42 $current=$current->suffix
43 while $index-$current->size-1<0
44 || $letters[$index-$current->size-1] ne $letter;
45 $current=$current->edge($letter), return
46 if defined $current->edge($letter);
47 my $suffix=$current->suffix;
48 my $found=$current;
49 $found->add_edge(
50 $letter,
51 $current=PNode->new(size=>$current->size+2, suffix=>$root));
52 return if $current->size==1;
53 $suffix=$suffix->suffix
54 while $letters[$index-$suffix->size-1] ne $letter;
55 $current->suffix($suffix->edge($letter));
56 }
57 method iterator {
58 my @queue=((map {[$imaginary_root->edge($_), $_]} $imaginary_root->edges), [$root, ""]);
59 sub {
60 {
61 return undef unless @queue;
62 my $element=shift @queue;
63 my ($node, $center)=$element->@*;
64 push @queue, (map {[$node->edge($_), $_.$center.$_]} $node->edges);
65 redo unless $center;
66 return $center;
67 }
68 }
69 }
70 BUILD {
71 $imaginary_root=PNode->new(size=>-1);
72 $imaginary_root->suffix($imaginary_root);
73 $root=PNode->new(size=>0, suffix=>$imaginary_root);
74 $current=$root;
75 my $s=decode_utf8($string,9);
76 $s=~s/\p{Punct}//g; #remove punctuation
77 $s=~s/\s*//g; #remove spaces
78 @letters=split '', unidecode(lc $s); #ignore case, remove accents
79 foreach(0..@letters-1){
80 $self->$add_letter($_);
81 }
82 }
83 };
84 foreach(@ARGV){
85 my $tree=PTree->new(string=>$_);
86 my $next=$tree->iterator;
87 while(my $p=$next->()){
88 say $p;
89 }
90 }
I define two classes PTree
to represent a palindrome tree and
PNode
to represent each of its nodes. Each PNode
has a size
, a
hash of its %edges
and a $suffix
. The PTree
has a $root
and an
$imaginary_root
, a $string
with which it is
initialized, and a $current
node. The PNode
has a method
to get the labels of all its edges
, and a method edge
to get a
succesor node from its label, and a method to add_edge
’s. To BUILD
the PTree
I
basically copied the previous program, using a private method to
$add_letter
’s. I added a method iterator
that returns an anonimous
subroutine with which we can generate all the palindromic fragments.
I test the program with the previous example.
./ch-2b.pl "Alba helada: la luz azul al alba heríale. El aire
habla, ¡la luz azul alada le habla!"
and I confirm I obtain the same results.
u
z
d
i
h
a
r
b
l
e
ada
zaz
lal
ala
ee
ladal
uzazu
lalal
leel
luzazul
aleela
aluzazula
ialeelai
laluzazulal
rialeelair
alaluzazulala
erialeelaire
herialeelaireh
aherialeelaireha
baherialeelairehab
lbaherialeelairehabl
albaherialeelairehabla
lalbaherialeelairehablal
alalbaherialeelairehablala
lalalbaherialeelairehablalal
ulalalbaherialeelairehablalalu
zulalalbaherialeelairehablalaluz
azulalalbaherialeelairehablalaluza
zazulalalbaherialeelairehablalaluzaz
uzazulalalbaherialeelairehablalaluzazu
luzazulalalbaherialeelairehablalaluzazul
aluzazulalalbaherialeelairehablalaluzazula
laluzazulalalbaherialeelairehablalaluzazulal
alaluzazulalalbaherialeelairehablalaluzazulala
dalaluzazulalalbaherialeelairehablalaluzazulalad
adalaluzazulalalbaherialeelairehablalaluzazulalada
ladalaluzazulalalbaherialeelairehablalaluzazulaladal
eladalaluzazulalalbaherialeelairehablalaluzazulaladale
heladalaluzazulalalbaherialeelairehablalaluzazulaladaleh
aheladalaluzazulalalbaherialeelairehablalaluzazulaladaleha
baheladalaluzazulalalbaherialeelairehablalaluzazulaladalehab
lbaheladalaluzazulalalbaherialeelairehablalaluzazulaladalehabl
albaheladalaluzazulalalbaherialeelairehablalaluzazulaladalehabla
Footnotes
1 I already paid the price trying to debug a stupid mistake
(I forgot a $
sigil within ->{...}
)