This week, we’re “defanging” IP addresses and calculating string scores, but both these tasks are easy enough we’ll be there in a minute.
Onward to Perl Weekly Challenge 272!
Task 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"
Approach
I have to admit I’d never heard of this process called “defanging” and IP address. My first thought was this was a process to turn the string into a regular expression that would match the IP address itself instead of any string with the numeric parts of the IP address separated by practically any character (since .
matches most single characters in a regex), but a quick google search on the term shows it’s also used to “prevent the IP address from being accidentally used in a context where it might represent an actionable hyperlink, such as in documentation or logs.”
I had to look away quickly, though, because the next thing I started seeing was how to accomplish that in several languages. Fortunately, I’d already decided how to do it—since [.]
looks like one of the ways to escape a period in a regular expression, I knew I wanted to use regular expressions to do this transformation.
Raku
We’re capturing the output of the regular expression match in Raku’s Match Variable, $/
.
sub defang($ip) {
$ip ~~ / (\d+) '.' (\d+) '.' (\d+) '.' (\d+) /;
return @$/.join('[.]');
}
$ raku/ch-1.raku
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"
View the entire Raku script for this task on GitHub.
Perl
But in Perl, we can use the easier to read @{^CAPTURE}
variable.
sub defang($ip) {
$ip =~ / (\d+) [.] (\d+) [.] (\d+) [.] (\d+) /x;
return join('[.]', @{^CAPTURE});
}
View the entire Perl script for this task on GitHub.
Python
Python returns a Match object that we can then call the method .group
on to get the captured strings.
import re
def defang(ip):
match = re.search(
r'(\d+) [.] (\d+) [.] (\d+) [.] (\d+)',
ip,
re.X
)
return '[.]'.join(match.group(1,2,3,4))
View the entire Python script for this task on GitHub.
Elixir
In Elixir, Regex.run/3 returns “all captured subpatterns including the complete matching string” unless you pass in the :capture
option specifying :all_but_first
, which is “all but the first matching subpattern, i.e. all explicitly captured subpatterns, but not the complete matching part of the string.”
def defang(ip) do
Regex.run(
~r/(\d+)[.](\d+)[.](\d+)[.](\d+)/,
ip,
capture: :all_but_first
)
|> Enum.join("[.]")
end
View the entire Elixir script for this task on GitHub.
Task 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
Approach
This is another straightforward problem: split the string into a list of characters, get the ASCII values of each of the characters, pull the first value off the list and store it in a $last
value, then loop over the remaining list pulling the next value off the list and putting it in $next
, calculating the absolute value of $last - $next
, adding that to a running sum, moving $next
to $last
and repeating until the list is empty.
Raku
I’m using a bunch of intermediate variables because I want to be able to use those values in different places, i.e, both the calculations and the running explanation.
sub score($str) {
my @chars = $str.comb;
my @vals = @chars.map: { .ord };
my @explain = ("ASCII values of characters:");
for @chars Z @vals -> ($c, $v) {
@explain.push: "$c = $v";
}
my @line1;
my @line2;
my $last = @vals.shift;
while (my $next = @vals.shift) {
@line1.push: "| $last - $next |";
@line2.push: abs($last - $next);
$last = $next;
}
@explain.push: "Score => " ~ @line1.join(" + ");
@explain.push: " => " ~ @line2.join(" + ");
my $score = [+] @line2;
@explain.push: " => " ~ $score;
return $score, @explain.join("\n");
}
$ raku/ch-2.raku
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: $str = "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: $str = "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
View the entire Raku script for this task on GitHub.
Perl
The Perl looks a lot like the Raku (or is that the other way around), but we need to pull in List::Util’s sum
and zip
functions to substitute for Raku’s built-in [+]
and Z
.
use List::Util qw( sum zip );
sub score($str) {
my @chars = split //, $str;
my @vals = map { ord($_) } @chars;
my @explain = ("ASCII values of characters:");
foreach my $z ( zip \@chars, \@vals ) {
my ($c, $v) = @$z;
push @explain, "$c = $v";
}
my @line1;
my @line2;
my $last = shift @vals;
while (my $next = shift @vals) {
push @line1, "| $last - $next |";
push @line2, abs($last - $next);
$last = $next;
}
push @explain, "Score => " . join(" + ", @line1);
push @explain, " => " . join(" + ", @line2);
my $score = sum @line2;
push @explain, " => " . $score;
return $score, join("\n", @explain);
}
View the entire Perl script for this task on GitHub.
Python
def score(strVal):
chars = [ c for c in strVal ]
vals = [ ord(c) for c in chars ]
explain = [ "ASCII values of characters:" ]
for c, v in zip(chars, vals):
explain.append(f"{c} = {v}")
line1 = []
line2 = []
last = vals.pop(0)
while vals:
next = vals.pop(0)
line1.append(f"| {last} - {next} |")
line2.append(abs(last - next))
last = next
explain.append("Score => " + " + ".join(line1))
explain.append(" => " + " + ".join(map(lambda i: str(i), line2)))
score = sum(line2)
explain.append(f" => {score}")
return score, "\n".join(explain)
View the entire Python script for this task on GitHub.
Elixir
This time, I remembered that to process a list and not produce a map of that list, I needed to process it recursively. Note that each recursive function has a halting form that when the list feeding it is empty, it returns the values we’ve been building.
# build up the lines like "h = 104"
def explain_mapping([], [], explain), do: explain
def explain_mapping(, [v | vals], explain) do
explain_mapping(chars, vals, explain ++ ["#{c} = #{v}"])
end
# process the list of codepoints and explain our calculations
def process_list([], _, line1, line2), do: {line1, line2}
def process_list([next | vals], last, line1, line2) do
line1 = line1 ++ ["| #{last} - #{next} |"]
line2 = line2 ++ [ abs(last - next) ]
process_list(vals, next, line1, line2)
end
def score(str) do
chars = String.graphemes(str)
vals = String.to_charlist(str)
# generate the first part of the explanation
explain = explain_mapping(
chars, vals, ["ASCII values of characters:"]
)
# get the first codepoint off the list
{last, vals} = List.pop_at(vals, 0)
# process the rest of the codepoints
{line1, line2} = process_list(vals, last, [], [])
# now format the last part of the explanation
line1str = Enum.join(line1, " + ")
explain = explain ++ ["Score => #{line1str}"]
line2str = Enum.join(line2, " + ")
explain = explain ++ [" => #{line2str}"]
scoreVal = Enum.sum(line2)
explain = explain ++ [" => #{scoreVal}"]
{ scoreVal, Enum.join(explain, "\n") }
end
View the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-272/packy-anderson