The 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 '[.]'.

#!/usr/bin/perl
use strict;
use warnings;

=begin
  Here my solution TASK #1 along with alternatives from Niels van Dijke and James Curtis-Smith. 
  I did some benchmark (https://metacpan.org/pod/Benchmark) with 'timethese': 
  James' join+split is the fastest solution, even when the validation in Niels' and my 
  solution is removed
=cut

use Data::Validate::IP qw(is_ipv4);

sub defangIP {

    if (is_ipv4($_[0])) {
        $_[0] =~ s/\./[.]/g; 
    }
    else {
        print("No valid IP address!");
    }	 
	
# Inspired by Niels van Dijke's oneliner which uses the necessary /r switch	
# (Niels uses for validation another module Net::IP)	
#    (is_ipv4($_[0])) ? $_[0] =~ s/\./[.]/gr : "No valid IP address!";	

# James Curtis-Smith solution:
#    join '[.]', split /\./, $_[0];
}

# TESTS
my $ip;

# Example 1
$ip = "1.1.1.1";
print(defangIP($ip), "\n"); # Output: 1[.]1[.]1[.]1

# Example 2
$ip = "255.101.1.0";
print(defangIP($ip), "\n"); # Output: 255[.]101[.]1[.]0

# Example 3
$ip = "123.234.345.001";
print(defangIP($ip), "\n"); # Output: "No valid IP address!

# Example 4
# From: https://metacpan.org/pod/Data::Validate::IP
# There are security implications to this around certain oddly formed addresses.
# Notably, an address like "010.0.0.1" is technically valid, but the operating 
# system will treat '010' as an octal number: '010.0.0.1' is interpreted as '8.0.0.1' 
# James' solution and Niels' original do not take that into account.
$ip = "010.0.0.1";
print(defangIP($ip), "\n"); # Output: "No valid IP address!

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:
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
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw(:all);

=begin
  Here is my solution for TASK #2, along with alternatives from 
  Niels van Dijke and James Curtis-Smith. I shared the benchmark results, 
  which show some variability when run multiple times. Nonetheless, James's 
  solution is the fastest.
=cut

# Reinier Maliepaard: function to calculate total sum using split and for
sub ascii_differences_sum_split_1 {
    my @chars = split(//, $_[0]);
    my $total_sum = 0;
    
    for my $i (0 .. $#chars - 1) {
        $total_sum += abs(ord($chars[$i]) - ord($chars[$i + 1]));
    }

    return($total_sum);
}

# James Curtis-Smith: function to calculate total sum using split and for
sub ascii_differences_sum_split_2 {
    my ($t, @l) = (0, split //, $_[0]);
	
    my $f = ord shift @l;
	
    ($t += abs($f - ord)), $f=ord for @l;
	
    $t;
}

# Niels van Dijke: function to calculate total sum using sum0 and slide
use List::Util qw(sum0);
use List::MoreUtils qw(slide);

sub ascii_differences_sum_slide {
  sum0 slide {abs(ord($b) - ord($a))} split '',$_[0]
}

# A fanciful creation by fans, combining various Elvish elements from Tolkien's work.
my $input_string = "Aearenuialinorosinaiantirnoitisirsiensinoit"; 

# Benchmark all three methods
timethese(1000000, {
    'Using split_1' => sub { ascii_differences_sum_split_1($input_string) },
    'Using split_2' => sub { ascii_differences_sum_split_2($input_string) },  
    'Using slide'   => sub { ascii_differences_sum_slide($input_string) },
});

=begin
Results:
Benchmark: timing 1000000 iterations of Using slide, Using split_1, Using split_2... Using slide: 7 wallclock secs ( 6.55 usr + 0.00 sys = 6.55 CPU) @ 152671.76/s (n=1000000) Using split_1: 7 wallclock secs ( 6.87 usr + 0.00 sys = 6.87 CPU) @ 145560.41/s (n=1000000) Using split_2: 6 wallclock secs ( 5.76 usr + 0.00 sys = 5.76 CPU) @ 173611.11/s (n=1000000)
=cut
To better understand James Curtis-Smith's solution, study the following code which also clarifies the role of James' implicit $_ (yes, I love using parentheses to make 'thoughts' clear ... my LISP background ...):
#!/usr/bin/perl
use strict;
use warnings;

sub ascii_differences_sum_split_2_alt
{
    my ( $t, @list ) = ( 0, split(//, $_[0]) );
	
    my $f = ord( shift(@list) );
	
    for (@list) {
        $t += abs( $f - ord($_) );
        $f = ord($_);
    }
    return($t);
}