IP Score
with Raku

by Arne Sommer

IP Score with Raku

[292] Published 8. June 2024 (and updated 11. June).

This is my response to The Weekly Challenge #272.

Challenge #272.1: Defang IP Address

You are given a valid IPv4 address.

Write a script to return the defanged version of the given IP address.

A defanged IP address replaces every period “.” with “[.]".

Example 1:
Input: $ip = "1.1.1.1"
Output: "1[.]1[.]1[.]1"
Example 2:
Input: $ip = "255.101.1.0"
Output: "255[.]101[.]1[.]0"
File: defanger
#! /usr/bin/env raku

subset IPv4 where /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
  && all($0,$1,$2,$3) < 256;       # [1]

unit sub MAIN (IPv4 $ipv4);        # [2]

say $ipv4.split('.').join('[.]');  # [3]

[1] A custom type with subset to be used on the argument to ensure a valid IPv4 address. Note the use of a simple regexp, together with an all junction to ensure that the integers do not exceed 255.

See docs.raku.org/language/typesystem#subset for more information about subset.

[2] Enforce the custom type.

[3] Split on the dots, and join with the defanged version (i.e. [.]).

Running it:

$ ./defanger 100.1.1.25
100[.]1[.]1[.]25

$ ./defanger 1.1.1.1
1[.]1[.]1[.]1

$ ./defanger 255.101.1.0
255[.]101[.]1[.]0

Looking good.

Verbose did not make any sense this time.

Note that I have given the variable the name $ipv4 to make it clear that we are dealing with ipv4. This gives better error messages:

$ ./defanger "0.0.0.0.0"
Usage:
  ./defanger <ipv4>

Also note that the regexp in [1] does not detect leading zeroes:

$ ./defanger "001.002.003.004"
001[.]002[.]003[.]004

Be aware that the \d character class in Raku will also match Unicode characters that have a digit property - as long as we do not treat them as strings (by quoting them):

> say so Ⅿ ~~ /\d/
True

> say Ⅿ.Int
1000

> say 'Ⅿ'.ord
8559

> say so ⑨ ~~ /\d/
True

> say so '⑨' ~~ /\d/
False

> say '⑨'.ord
9320

ord is decribed in the next part of the challenge.

Challenge #272.2: String Score

You are given a string, $str.

Write a script to return the score of the given string.

The score of a string is defined as the sum of the absolute difference between the ASCII values of adjacent characters.

Example 1:
Input: $str = "hello"
Output: 13

ASCII values of characters:
h = 104
e = 101
l = 108
l = 108
o = 111

Score => |104 - 101| + |101 - 108| + |108 - 108| + |108 - 111|
      => 3 + 7 + 0 + 3
      => 13
Example 2:
Input: "perl"
Output: 30

ASCII values of characters:
p = 112
e = 101
r = 114
l = 108

Score => |112 - 101| + |101 - 114| + |114 - 108|
      => 11 + 13 + 6
      => 30
Example 3:
Input: "raku"
Output: 37

ASCII values of characters:
r = 114
a = 97
k = 107
u = 117

Score => |114 - 97| + |97 - 107| + |107 - 117|
      => 17 + 10 + 10
      => 37

File: string-score
#! /usr/bin/env raku

unit sub MAIN ($str where $str.chars > 1,  # [1]
               :v(:$verbose)); 

my $adjacents := gather                    # [2]
{
  my @str = $str.comb;                     # [3]

  my $first = @str.shift;                  # [4]

  while @str.chars                         # [5]
  {
    my $second = @str.shift;               # [6]

    take ($first, $second);                # [7]

    $first = $second;                      # [8]
  }
}

my $score = 0;                             # [9]

for $adjacents -> @pair                    # [10]
{
  my $first  = @pair[0];                   # [11]
  my $second = @pair[1];                   # [12]

  my $first_code  = $first.ord;            # [13]
  my $second_code = $second.ord;           # [14]
  my $difference  = abs ($first_code - $second_code);  # [15]

  $score += $difference;                   # [16]

  say ": Adjacents: '$first' ($first_code) - '$second' ($second_code) \
    = $difference -> $score" if $verbose;
}

say $score;                                # [17]

[1] Ensure that the string has at least two characters.

[2] Set up a sequence of two adjacent characters, with gather (here) and take (in [7]).

See my Raku Gather, I Take article or docs.raku.org/language/control#gather/take for more information about gather/take.

[3] Get a list of individual characters.

[4] Get the first character.

[5] A loop, as long as we have more characters.

[6] Get the second character.

[7] Return the pair, with take.

[8] Prepare for the next iteration of the loop, where the current second character will be the first.

[9] The total score will end up here.

[10] Iterate over the adjacents, as set up in [2].

[11] Get the first value,

[12] and the second.

[13] Get the ascii value (UTF-8 codepoint, really) of the first character with ord.

See docs.raku.org/routine/ord for more information about ord.

[14] Ditto for the second one.

[15] Get the difference, as a non-zero value.

[16] Add the current difference to the total score.

[17] Print the result.

Running it:

$ ./string-score hello
13

$ ./string-score perl
30

$ ./string-score raku
37

Looking good.

With verbose mode:

$ ./string-score -v hello
: Adjacents: 'h' (104) - 'e' (101) = 3 -> 3
: Adjacents: 'e' (101) - 'l' (108) = 7 -> 10
: Adjacents: 'l' (108) - 'l' (108) = 0 -> 10
: Adjacents: 'l' (108) - 'o' (111) = 3 -> 13
13

$ ./string-score -v perl
: Adjacents: 'p' (112) - 'e' (101) = 11 -> 11
: Adjacents: 'e' (101) - 'r' (114) = 13 -> 24
: Adjacents: 'r' (114) - 'l' (108) = 6 -> 30
30

$ ./string-score -v raku
: Adjacents: 'r' (114) - 'a' (97) = 17 -> 17
: Adjacents: 'a' (97) - 'k' (107) = 10 -> 27
: Adjacents: 'k' (107) - 'u' (117) = 10 -> 37
37

And that's it.

Update 11. June: Over-Engineering

You may have noticed that I am somewhat fond of gather/take. It is indeed a powerful mechanism, but in this case it is overkill.

A simple loop would do just fine, and cloaking it as a map gives us what is (almost) a one liner:

File: string-score-map
#! /usr/bin/env raku

unit sub MAIN ($str where $str.chars > 1);

(0 .. $str.chars -2).map({ abs ($str.substr($_, 1).ord
                              - $str.substr($_ +1, 1).ord ) }).sum.say;

Running it gives the expected result:

$ ./string-score-map hello
13

$ ./string-score-map perl
30

$ ./string-score-map raku
37

And that's it. Really, this time.