This is my response to The Weekly Challenge #383.
Input: $list1 = ("great", "acting")
$list2 = ("fine", "drama")
$list3 = (["great", "fine"], ["acting", "drama"])
Output: true
Example 2:
Input: $list1 = ("apple", "pie")
$list2 = ("banana", "pie")
$list3 = (["apple", "peach"], ["peach", "banana"])
Output: false
Example 3:
Input: $list1 = ("perl4", "python")
$list2 = ("raku", "python")
$list3 = (["perl4", "perl5", "raku"])
Output: true
Example 4:
Input: $list1 = ("enjoy", "challenge")
$list2 = ("love", "weekly", "challenge")
$list3 = (["enjoy", "love"])
Output: false
Example 5:
Input: $list1 = ("fast", "car")
$list2 = ("quick", "vehicle")
$list3 = (["quick", "fast"], ["vehicle", "car"])
Output: true
Some observations about the «similar words map», used to translate the words in list1 to list2. (And yes, I have chosen to call list3 a «translation list»):
#! /usr/bin/env raku
unit sub MAIN ($list1, $list2, $list3, :v(:$verbose));
my @list1 = $list1.words;
my @list2 = $list2.words;
my @list3 = $list3.split("|")>>.words;
unless @list1.elems == @list2.elems
{
say ": List 1&2 must have the same length" if $verbose;
say False;
exit;
}
my %alias;
for @list3 -> @map;
{
for @map -> $left;
{
for @map -> $right
{
next if $left eq $right;
%alias{$left}.push: $right;
say ": Alias '$left' -> '$right'" if $verbose;
}
}
}
my $similar = True;
for ^@list1.elems -> $index
{
say ": Index $index: Compare '@list1[$index]' and \
'@list2[$index]'" if $verbose;
if @list1[$index] eq @list2[$index]
{
say ": - Same word" if $verbose;
next;
}
if any(@(%alias{@list1[$index]})) eq @list2[$index]
{
say ": - Aliased to same word" if $verbose;
next;
}
say ": - No match" if $verbose;
$similar = False;
last;
}
say $similar;
[3] Three strings, one for each of the arrays.
[4+6] Split the string into words with words.
See docs.raku.org/routine/words for more information about words.
[7] This one is a bit trickier. We start by splitting the string
on | (zero or more). Then we apply words on each of
those strings, giving a two dimentional array.
[9] The first two lists must have the same number of words. If not, report failure and exit.
[16] The translation lists are ending up here, in an alias hash.
[18] Iterate over the translation lists. (The first example has two of them.)
[20] Iterate over the values in the current translation list, using the current value as the left (the hash key) side if the alias.
[22] As [20], but now the current value is used as the right (the hash value) side.
[24] The two loops gives us an alias from the value to itself, and we ignore that one.
[25] Register the alias. Note the use of push to a hash. That
will generate a list, so that we can store several aliases for the same
key.
[31] Assume success.
[33] Iterate over the word indices in the lists (lists 1 and 2 have the same length, courtesy of [9]).
[38] Go to the next iteration if the words are equal.
[44] Go to the next iteration if any of the aliases
for the word in the first list are equal to the word in the second list.
See docs.raku.org/routine/any for more information about any.
[51] We have failed.
[52] There is no need to go on, as we cannot recover from failure.
[55] Report success or failure.
Running it:
$ ./similar-list "great acting" "fine drama" "great fine | acting drama"
True
$ ./similar-list "apple pie" "banana pie" "apple peach | peach banana"
False
$ ./similar-list "perl4 python" "raku python" "perl4 perl5 raku"
True
$ ./similar-list "enjoy challenge" "love weekly challenge" "enjoy love"
False
$ ./similar-list "fast car" "quick vehicle" "quick fast | vehicle car"
True
Looking good.
With verbose mode:
$ ./similar-list -v "great acting" "fine drama" "great fine | acting drama"
: Alias 'great' -> 'fine'
: Alias 'fine' -> 'great'
: Alias 'acting' -> 'drama'
: Alias 'drama' -> 'acting'
: Index 0: Compare 'perl4' and 'raku'
: - Aliased to same word
: Index 1: Compare 'python' and 'python'
: - Same word
True
$ ./similar-list -v "apple pie" "banana pie" "apple peach | peach banana"
: Alias 'apple' -> 'peach'
: Alias 'peach' -> 'apple'
: Alias 'peach' -> 'banana'
: Alias 'banana' -> 'peach'
: Index 0: Compare 'apple' and 'banana'
: No match
False
$ ./similar-list -v "perl4 python" "raku python" "perl4 perl5 raku"
: Alias 'perl4' -> 'perl5'
: Alias 'perl4' -> 'raku'
: Alias 'perl5' -> 'perl4'
: Alias 'perl5' -> 'raku'
: Alias 'raku' -> 'perl4'
: Alias 'raku' -> 'perl5'
: Index 0: Compare 'perl4' and 'raku'
: - Aliased to same word
: Index 1: Compare 'python' and 'python'
: - Same word
True
$ ./similar-list -v "enjoy challenge" "love weekly challenge" "enjoy love"
: List 1&2 must have the same length
False
$ ./similar-list -v "fast car" "quick vehicle" "quick fast | vehicle car"
: Alias 'quick' -> 'fast'
: Alias 'fast' -> 'quick'
: Alias 'vehicle' -> 'car'
: Alias 'car' -> 'vehicle'
: Index 0: Compare 'fast' and 'quick'
: - Aliased to same word
: Index 1: Compare 'car' and 'vehicle'
: - Aliased to same word
True
Input: $color = "#F4B2D1"
Output: "#FF99CC"
Red: F4 (Decimal 244), closer to 255 => FF
Green: B2 (Decimal 178), closer to 153 => 99
Blue: D1 (Decimal 209), closer to 204 => CC
So the nearest RGB: "#FF99CC"
Example 2:
Input: $color = "#15E6E5"
Output: "#00FFCC"
Red: 15 (Decimal 21), closer to 0 => 00
Green: E6 (Decimal 230), closer to 255 => FF
Blue: E5 (Decimal 229), closer to 204 => CC
Example 3:
Input: $color = "#191A65"
Output: "#003366"
Red: 19 (Decimal 25), closer to 0 => 00
Green: 1A (Decimal 26), closer to 51 => 33
Blue: 65 (Decimal 101), closer to 102 => 66
Example 4:
Input: $color = "#2D5A1B"
Output: "#336633"
Red: 2D (Decimal 45), closer to 51 => 33
Green: 5A (Decimal 90), closer to 102 => 66
Blue: 1B (Decimal 27), closer to 51 => 33
Example 5:
Input: $color = "#00FF66"
Output: "#00FF66"
Red: 00 (Decimal 0), closer to 0 => 00
Green: FF (Decimal 255), closer to 255 => FF
Blue: 66 (Decimal 102), closer to 102 => 66
subset HEX2 where * ~~ /^ <[0 .. 9 A .. F]> ** 2 $/;
unit sub MAIN ($color where $color
~~ /^ "#" <[0 .. 9 A .. F]> ** 6 $/,
:v(:$verbose));
sub nearest-hex (HEX2 $hex)
{
my $decimal = $hex.parse-base(16);
say ":Hex $hex = Decimal $decimal" if $verbose;
return '00' if $decimal <= 25; # 0
return '33' if $decimal <= 76; # 51
return '66' if $decimal <= 127; # 102
return '99' if $decimal <= 178; # 153
return 'CC' if $decimal <= 229; # 204
return 'FF'; # 255
}
say "#"
~ nearest-hex($color.substr(1,2))
~ nearest-hex($color.substr(3,2))
~ nearest-hex($color.substr(5,2));
[1] A custom type used to ensure a two-digit HEX value, set up with
subset, and used in [7].
See docs.raku.org/language/typesystem#subset for more information about subset.
[3] The alternative to subset, inline the where clause.
We require a # followed by a six digit HEX value.
[7] Procedure rounding a two digit HEX value to the nearest web-safe value.
[9] Convert the HEX value into decimal with
parse-base.
See docs.raku.org/routine/parse-base for more information about parse-base.
[13-18] Return the nearest web-safe HEX value.
[21] Print the result of web-safing the three groups of two HEX digits.
Running it:
$ ./nearest-rgb "#F4B2D1"
#FF99CC
$ ./nearest-rgb "#15E6E5"
#00FFCC
$ ./nearest-rgb "#191A65"
#003366
$ ./nearest-rgb "#2D5A1B"
#336633
$ ./nearest-rgb "#00FF66"
#00FF66
Looking good.
With verbose mode:
$ ./nearest-rgb -v "#F4B2D1"
:Hex F4 = Decimal 244
:Hex B2 = Decimal 178
:Hex D1 = Decimal 209
#FF99CC
$ ./nearest-rgb -v "#15E6E5"
:Hex 15 = Decimal 21
:Hex E6 = Decimal 230
:Hex E5 = Decimal 229
#00FFCC
$ ./nearest-rgb -v "#191A65"
:Hex 19 = Decimal 25
:Hex 1A = Decimal 26
:Hex 65 = Decimal 101
#003366
$ ./nearest-rgb -v "#2D5A1B"
:Hex 2D = Decimal 45
:Hex 5A = Decimal 90
:Hex 1B = Decimal 27
#336633
$ ./nearest-rgb -v "#00FF66"
:Hex 00 = Decimal 0
:Hex FF = Decimal 255
:Hex 66 = Decimal 102
#00FF66
And that's it.