This is my response to The Weekly Challenge #272.
IPv4
address.
Input: $ip = "1.1.1.1"
Output: "1[.]1[.]1[.]1"
Example 2:
Input: $ip = "255.101.1.0"
Output: "255[.]101[.]1[.]0"
#! /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.
$str
.
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.
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:
#! /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.