aboutsummaryrefslogtreecommitdiff
path: root/challenge-010
diff options
context:
space:
mode:
authorGustavo L. de M. Chaves <gustavo@cpqd.com.br>2019-05-31 22:51:20 -0300
committerGustavo L. de M. Chaves <gustavo@cpqd.com.br>2019-05-31 22:51:20 -0300
commitb2a5c076ec32c123540821bcbf3e51e9e8fd4fa1 (patch)
tree8b6ff04062a95fd1db28cebd5578045994941d74 /challenge-010
parent6135ea57e1b789c67981e2de52c2bc9c666a0f74 (diff)
downloadperlweeklychallenge-club-b2a5c076ec32c123540821bcbf3e51e9e8fd4fa1.tar.gz
perlweeklychallenge-club-b2a5c076ec32c123540821bcbf3e51e9e8fd4fa1.tar.bz2
perlweeklychallenge-club-b2a5c076ec32c123540821bcbf3e51e9e8fd4fa1.zip
Gustavo Chaves Perl 5 solutions to the challenge 010
Diffstat (limited to 'challenge-010')
-rw-r--r--challenge-010/gustavo-chaves/perl5/README.pod61
-rwxr-xr-xchallenge-010/gustavo-chaves/perl5/ch-1.pl79
-rwxr-xr-xchallenge-010/gustavo-chaves/perl5/ch-2.pl97
3 files changed, 237 insertions, 0 deletions
diff --git a/challenge-010/gustavo-chaves/perl5/README.pod b/challenge-010/gustavo-chaves/perl5/README.pod
new file mode 100644
index 0000000000..6781160863
--- /dev/null
+++ b/challenge-010/gustavo-chaves/perl5/README.pod
@@ -0,0 +1,61 @@
+=pod
+
+=encoding utf8
+
+=head1 #1 Roman numerals
+
+=over 4
+
+Write a script to encode/decode Roman numerals. For example, given Roman numeral
+CCXLVI, it should return 246. Similarly, for decimal number 39, it should return
+XXXIX. Checkout L<wikipedia page|https://en.wikipedia.org/wiki/Roman_numerals>
+for more information.
+
+=back
+
+In the Wikipedia I learned that there are two different notations for Roman
+numerals: additive and subtractive. They differ in how they render some
+numbers, like these:
+
+ +--------+----------+-------------+
+ | Arabic | Additive | Subtractive |
+ +--------+----------+-------------+
+ | 4 | IIII | IV |
+ | 9 | IX | VIIII |
+ | 19 | XVIIII | XIX |
+ | 90 | XC | LXXXX |
+ +--------+----------+-------------+
+
+My solution (ch-1.pl) accepts a list of arabic or roman numerals as arguments,
+detects in which system they are and converts them to the other system. The
+convertion from arabic to roman always uses the additive notation, but the
+convertion from roman to arabic hopefully understands both notations.
+
+In particular, this means that my solution does not fully comply with the
+specification, because it asks that 39 should be converted to XXXIX, which is in
+subtractive notation. My script converts 39 to XXXVIIII. Like this:
+
+ $ ./ch-1.pl CCXLVI 246 XXXIX 39
+ CCXLVI == 246
+ 246 == CCXXXXVI
+ XXXIX == 39
+ 39 == XXXVIIII
+
+=head1 #2 Ranking
+
+=over 4
+
+Write a script to find Jaro-Winkler distance between two strings. For more
+information check L<wikipedia
+page|https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance>.
+
+=back
+
+I found it hard to fully grasp the Definition Section in the English wikipedia
+page. Fortunately, the L<French
+page|https://fr.wikipedia.org/wiki/Distance_de_Jaro-Winkler> has more examples
+which allowed me to get going, although it seems to be confounding the Jaro
+Distance with the Jaro Similarity.
+
+I'm not sure about the correctness of my solution. At least, it calculates
+correctly the three examples in the French page.
diff --git a/challenge-010/gustavo-chaves/perl5/ch-1.pl b/challenge-010/gustavo-chaves/perl5/ch-1.pl
new file mode 100755
index 0000000000..c816f5aed5
--- /dev/null
+++ b/challenge-010/gustavo-chaves/perl5/ch-1.pl
@@ -0,0 +1,79 @@
+#!/usr/bin/env perl
+
+use 5.026;
+use strict;
+use autodie;
+use warnings;
+
+sub main {
+ foreach (@ARGV) {
+ if (/^\d+$/) {
+ say "$_ == ", arabic_to_roman($_);
+ } elsif (/^[IVXLCDM]+$/) {
+ say "$_ == ", roman_to_arabic($_);
+ } else {
+ die "'$_' is neither an arabic nor a roman numeral\n";
+ }
+ }
+ return 0;
+}
+
+my %powers = (
+ M => 1000,
+ D => 500,
+ C => 100,
+ L => 50,
+ X => 10,
+ V => 5,
+ I => 1,
+);
+
+my @powers =
+ map {[$_ => $powers{$_}]}
+ sort {$powers{$b} <=> $powers{$a}}
+ keys %powers;
+
+sub arabic_to_roman {
+ my ($arabic) = @_;
+
+ my $roman = '';
+
+ foreach my $power (@powers) {
+ use integer;
+ my ($letter, $base) = @$power;
+ if (my $multiple = $arabic / $base) {
+ $roman .= $letter x $multiple;
+ }
+ $arabic %= $base;
+ }
+
+ return $roman;
+}
+
+sub roman_to_arabic {
+ my ($roman) = @_;
+
+ my $arabic = my $accumulator = 0;
+ my $last_power = 10000; # greater than the biggest power
+
+ foreach my $letter (split //, $roman) {
+ my $power = $powers{$letter};
+ if ($power < $last_power) {
+ $arabic += $accumulator;
+ $accumulator = $power;
+ } elsif ($power == $last_power) {
+ # additive notation
+ $accumulator += $power;
+ } else {
+ # subtractive notation
+ $arabic += $accumulator - 2 * $last_power + $power;
+ $accumulator = 0;
+ }
+ $last_power = $power;
+ }
+ $arabic += $accumulator;
+
+ return $arabic;
+}
+
+main();
diff --git a/challenge-010/gustavo-chaves/perl5/ch-2.pl b/challenge-010/gustavo-chaves/perl5/ch-2.pl
new file mode 100755
index 0000000000..2e84a08009
--- /dev/null
+++ b/challenge-010/gustavo-chaves/perl5/ch-2.pl
@@ -0,0 +1,97 @@
+#!/usr/bin/env perl
+
+use 5.026;
+use strict;
+use autodie;
+use warnings;
+use List::Util qw(min max);
+
+sub main {
+ die "usage: $0 STRING STRING\n" unless @ARGV == 2;
+ my ($s1, $s2) = @ARGV;
+
+ say jaro_winkler_distance($s1, $s2);
+
+ return 0;
+}
+
+sub jaro_winkler_distance {
+ my ($s1, $s2) = @_;
+
+ return 1 - jaro_winkler_similarity($s1, $s2);
+}
+
+sub jaro_winkler_similarity {
+ my ($s1, $s2) = @_;
+
+ my $similarity = jaro_similarity($s1, $s2);
+ my $length = common_prefix_length($s1, $s2);
+ my $scaling_factor = 0.1;
+
+ return $similarity + $length * $scaling_factor * (1 - $similarity);
+}
+
+sub jaro_similarity {
+ my ($s1, $s2) = @_;
+
+ my ($matchings, $transpositions) = matching_characters_and_transpositions($s1, $s2);
+
+ return
+ $matchings == 0
+ ? 0
+ : ($matchings/length($s1) + $matchings/length($s2) + ($matchings-$transpositions)/$matchings) / 3;
+}
+
+sub matching_characters_and_transpositions {
+ my ($s1, $s2) = @_;
+
+ my $l1 = length $s1;
+ my $l2 = length $s2;
+
+ my $farthest = int(max($l1, $l2)/2) - 1;
+
+ my $m1 = matching_characters($s1, $s2, $l1, $l2, $farthest);
+ my $m2 = matching_characters($s2, $s1, $l2, $l1, $farthest);
+
+ my $matchings = min(length $m1, length $m2);
+
+ my $transpositions = 0;
+ for (my $i=0; $i<$matchings; ++$i) {
+ if (substr($m1, $i, 1) ne substr($m2, $i, 1)) {
+ ++$transpositions;
+ }
+ }
+
+ return ($matchings, int($transpositions/2));
+}
+
+sub matching_characters {
+ my ($s1, $s2, $l1, $l2, $farthest) = @_;
+
+ my $matching = '';
+
+ for my $i (0 .. $l1) {
+ my $char = substr($s1, $i, 1);
+ for my $j (max(0, $i-$farthest) .. min($i+$farthest, $l2-1)) {
+ if ($char eq substr($s2, $j, 1)) {
+ $matching .= $char;
+ last;
+ }
+ }
+ }
+
+ return $matching;
+}
+
+sub common_prefix_length {
+ my ($s1, $s2) = @_;
+
+ my $length = 0;
+ for my $i (0 .. 3) {
+ last if substr($s1, $i, 1) ne substr($s2, $i, 1);
+ ++$length;
+ }
+ return $length;
+}
+
+main();