Similar RGB List
with Raku

by Arne Sommer

Similar RGB List with Raku

[408] Published 22. July 2026.

This is my response to The Weekly Challenge #383.

#383.1 Similar List You are given three list of strings.

Write a script to find out if the first two list are similar with the help the third list. The third list contains the similar words map.

Example 1:
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»):

  • In the first example the translation from list1 to list2 goes from the first to the second value of the pairs in list3. But in the fifth example the direction is the other way round. So the translation is a two way list (or street), if we always start with list 1
  • In the third example we have three elements in the (one and only) translation list. This gives a three way relationship; all the words can be translated to the other two
  • In the second example we have a repetition in the two translation lists. This means that «peach» has «apple» and «banana» as possible translations, but «apple» and «banana» have «peach» only (and not each other).
File: similar-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

#383.2 Nearest RGB You are given a 6-digit hex color.

Write a script to round the RGB channels to the nearest web-safe value and return the nearest RGB color.

00 (0), 33 (51), 66 (102), 99 (153), CC (204) and FF (255)

Example 1:
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
File: nearest-rgb
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.