The IPv4 Product, Raku Edition

by Arne Sommer

The IPv4 Product, Raku Edition

[74] Published 23. May 2020.

This is my response to the Perl Weekly Challenge #061.

Challenge #061.1: Product SubArray

Given a list of 4 or more numbers, write a script to find the contiguous sublist that has he maximum product. The length of the sublist is irrelevant; your job is to maximize the product.

Example

Input: [ 2, 5, -1, 3 ]

Output: [ 2, 5 ] which gives maximum product 10.

The values are stated to be numbers, so not restrictive in any way. The Raku type Numeric fits the bill.

File: psu0 (partial)
unit sub MAIN (*@list where @list.elems >= 4 && all(@list) ~~ Numeric);
               # [1] ###### # [1a] ######## ## # [1b] ##############

[1] A slurpy array collecting all the arguments. It allows zero arguments, so we explicitly demand 4 or more [1a]. Then we ensure that they all are of the Numeric type with an all Junction [1b].

See docs.raku.org/type/Signature#index-entry-slurpy_argument for more information about slurpy arguments.

See docs.raku.org/routine/all for more information about all, and docs.raku.org/type/Junction for more information about Junctions.

See docs.raku.org/type/Numeric for more information about the Numeric type.

Then two loops iterating over all the possible contiguous sublists (with at least two items), and just printing them.

File: psu0 (partial)
for 0 .. @list.end - 1 -> $left                            # [2]
{
  for $left + 1 .. @list.end -> $right                     # [3]
  {
    say ": (L:$left R:$right) -> @list[$left .. $right]";  # [4]
  }
}

[2] Iterate the start of the sublist,

[3] and the end.

[4] Show that we got it right.

Running it:

$ raku psu0 1 2 3
Usage:
  psu0 [<list> ...]

$ raku psu0 1 2 3 4
: (L:0 R:1) -> 1 2
: (L:0 R:2) -> 1 2 3
: (L:0 R:3) -> 1 2 3 4
: (L:1 R:2) -> 2 3
: (L:1 R:3) -> 2 3 4
: (L:2 R:3) -> 3 4

$ raku psu0 1 2 3 4 5 6
: (L:0 R:1) -> 1 2
: (L:0 R:2) -> 1 2 3
: (L:0 R:3) -> 1 2 3 4
: (L:0 R:4) -> 1 2 3 4 5
: (L:0 R:5) -> 1 2 3 4 5 6
: (L:1 R:2) -> 2 3
: (L:1 R:3) -> 2 3 4
: (L:1 R:4) -> 2 3 4 5
: (L:1 R:5) -> 2 3 4 5 6
: (L:2 R:3) -> 3 4
: (L:2 R:4) -> 3 4 5
: (L:2 R:5) -> 3 4 5 6
: (L:3 R:4) -> 4 5
: (L:3 R:5) -> 4 5 6
: (L:4 R:5) -> 5 6

Looking good. It requires 4 or more elements, and we got all the contiguous sublists with at least two elements

Then we can do the real work, inside the double loops:

File: psu1
unit sub MAIN (*@list where @list.elems >= 4 && all(@list) ~~ Numeric,
               :$verbose);                                 # [1]

my $maximum = 0;                                           # [2]
my @max_array;                                             # [3]

for 0 .. @list.end - 1 -> $left
{
  for $left + 1 .. @list.end -> $right
  {
    my @current = @list[$left .. $right];                  # [4]
    my $product = [*] @current;                            # [5]

    say ": (L:$left R:$right) -> @urrent[] -> *$product" if $verbose; # [1]
 
    if $product == $maximum                                # [6]
    {
      @max_array.push: @current;                           # [6a]
    }
    elsif $product > $maximum                              # [7]
    {
      $maximum   = $product;                               # [7a]
      @max_array = @current,;                              # [7b]
    }
  }
}

say @max_array;                                            # [8]

[1] The old behaviour, available with the «--verbose» command line option.

[2] The higest value, so far.

[3] The array with the highest value, so far.

[4] Get the current sublist.

[5] Get the product of the sublist, using the Reduction Metaoperator [*] (where * is the operator) to multiply all the values in the sublist.

[6] If the current sublist has the same value as the currently highest, add it to the list [6a].

[7] If it is higher, set the new maxiumum value [7a], clear the list and add the sublist as a sublist (the trailing comma, which is the list operator) [7b]. The reult is a list with one element (which is a list).

[8] Print the result, one or more sublists.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about Reduction metaoperators.

See docs.raku.org/routine/, for more information about the list operator ,.

The list given in the challenge gives the correct result:

$ raku psu1 2 5 -1 3
[2 5]

$ raku psu1 --verbose 2 5 -1 3
: (L:0 R:1) -> 2 5 ---> 10
: (L:0 R:2) -> 2 5 -1 ---> -10
: (L:0 R:3) -> 2 5 -1 3 ---> -30
: (L:1 R:2) -> 5 -1 ---> -5
: (L:1 R:3) -> 5 -1 3 ---> -15
: (L:2 R:3) -> -1 3 ---> -3

Let us try some other values:

$ raku psu1 -10 1 -1 1 -12
[[1 -1 1 -12] [-1 1 -12]]

$ raku psu1 --verbose -10 1 -1 1 -12
: (L:0 R:1) -> -10 1 ---> -10
: (L:0 R:2) -> -10 1 -1 ---> 10
: (L:0 R:3) -> -10 1 -1 1 ---> 10
: (L:0 R:4) -> -10 1 -1 1 -12 ---> -120
: (L:1 R:2) -> 1 -1 ---> -1
: (L:1 R:3) -> 1 -1 1 ---> -1
: (L:1 R:4) -> 1 -1 1 -12 ---> 12
: (L:2 R:3) -> -1 1 ---> -1
: (L:2 R:4) -> -1 1 -12 ---> 12
: (L:3 R:4) -> 1 -12 ---> -12
[[1 -1 1 -12] [-1 1 -12]]

Note that two different sublists gives the same answer, so the program prints both.

We can add code showing the maximum product:

File: psu2 (changes only)
unit sub MAIN (*@list where @list.elems >= 4 && all(@list) ~~ Numeric,
               :$verbose, :$product);
print "[*:$maximum] -> " if $product;
say @max_array;

Running it:

$ raku psu2 -10 1 -1 1 -12
[[1 -1 1 -12] [-1 1 -12]]

$ raku psu2 --product -10 1 -1 1 -12
[*:12] -> [[1 -1 1 -12] [-1 1 -12]]

$ raku psu2 --product --verbose -10 1 -1 1 -12
: (L:0 R:1) -> -10 1 ---> -10
: (L:0 R:2) -> -10 1 -1 ---> 10
: (L:0 R:3) -> -10 1 -1 1 ---> 10
: (L:0 R:4) -> -10 1 -1 1 -12 ---> -120
: (L:1 R:2) -> 1 -1 ---> -1
: (L:1 R:3) -> 1 -1 1 ---> -1
: (L:1 R:4) -> 1 -1 1 -12 ---> 12
: (L:2 R:3) -> -1 1 ---> -1
: (L:2 R:4) -> -1 1 -12 ---> 12
: (L:3 R:4) -> 1 -12 ---> -12
[*:12] -> [[1 -1 1 -12] [-1 1 -12]]

Looking good.

A more extreme example:

$ raku psu2 --product --verbose -10 1 1 1 1
: (L:0 R:1) -> -10 1 ---> -10
: (L:0 R:2) -> -10 1 1 ---> -10
: (L:0 R:3) -> -10 1 1 1 ---> -10
: (L:0 R:4) -> -10 1 1 1 1 ---> -10
: (L:1 R:2) -> 1 1 ---> 1
: (L:1 R:3) -> 1 1 1 ---> 1
: (L:1 R:4) -> 1 1 1 1 ---> 1
: (L:2 R:3) -> 1 1 ---> 1
: (L:2 R:4) -> 1 1 1 ---> 1
: (L:3 R:4) -> 1 1 ---> 1
[*:1] -> [[1 1] [1 1 1] [1 1 1 1] [1 1] [1 1 1] [1 1]]

Note the repetitions in the sublists. They have the same values, but come from different parts of the original list. We can get rid of them (as we report the values, and not the positions, so they don't make sense):

File: psu3 (changes only)
say @max_array.unique(:with(&[eqv])); # [1]

[1] Comparing lists (as we have a list of lists) as done behind the scenes by unique doesn't work, but we can ask it to do a recursive comparison with the Equivalence Operator eqv. The :with argument takes a procedure, but we can use brackets to specify an operator. The & part is there to get a reference and not calling it straight away.

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

See docs.raku.org/routine/eqv for more information about the Equivalence Operator eqv.

Running it:

$ raku psu3 --product --verbose -10 1 1 1 1
: (L:0 R:1) -> -10 1 ---> -10
: (L:0 R:2) -> -10 1 1 ---> -10
: (L:0 R:3) -> -10 1 1 1 ---> -10
: (L:0 R:4) -> -10 1 1 1 1 ---> -10
: (L:1 R:2) -> 1 1 ---> 1
: (L:1 R:3) -> 1 1 1 ---> 1
: (L:1 R:4) -> 1 1 1 1 ---> 1
: (L:2 R:3) -> 1 1 ---> 1
: (L:2 R:4) -> 1 1 1 ---> 1
: (L:3 R:4) -> 1 1 ---> 1
[*:1] -> [[1 1] [1 1 1] [1 1 1 1]]

An finally, trying with some creative values:

$ raku psu3 --product 10 1/3 -11 1
[*:3.333333] -> ([10 1/3])

$ raku psu3 --product 30 0.67 -11 1
[*:20.1] -> ([30 0.67])

The Zero Observation

The program works fine, as long as we have the requirement of at least 4 values in the input list. The lowest possible value for the product is 0 (as shown in the table below), so the initial lowest value of 0 in the code isn't a problem.

Initial ValuesChosen SublistProduct
[0 0 0 0][0 0]0
[0 -12 0 0][0 0]0
[1 -12 1 1][1 1]1
[1 -12 1 -1][-12 1 -1]12

If we reduce the requirement on the input list to be 2 or more items, the initial value of 0 is a problem. Setting it to -Inf solves that.

The challenge was specific about the limit, so I have added a command line option where we can override the default value 4.

The final program:

File: psu
unit sub MAIN (
    :$min = 4,                                                  # [1]
    *@list where @list.elems >= $min && all(@list) ~~ Numeric,  # [2]
    :$verbose,
    :$product
);

say ": Minimum number of elements: $min" if $verbose;

my $maximum = -Inf;
my @max_array;

for 0 .. @list.end - 1 -> $left
{
  for $left + 1 .. @list.end -> $right
  {
    my @current = @list[$left .. $right];
    my $product = [*] @current;
    
    say ": (L:$left R:$right) -> @current[] ---> $product" if $verbose;

    if $product == $maximum
    {
      @max_array.push: @current;
    }
    elsif $product > $maximum
    {
      $maximum   = $product;
      @max_array = ();
      @max_array.push: @current;
    }
  }
}

print "[*:$maximum] -> " if $product;
say @max_array.unique(:with(&[eqv]));

[1] We have to declare the new varible before using it (in [2]).

[2] Using the new variable to check the length of the array.

See docs.raku.org/type/Num#index-entry-Inf_(definition) for more information about Inf.

Trying it out with some potentially problematic values:

$ raku psu --min=2 --product Inf Inf
[*:Inf] -> ([Inf Inf])

$ raku psu --min=2 --product Inf 1
[*:Inf] -> ([Inf 1])

$ raku psu --min=2 --product Inf 0
[*:-Inf] -> ()

$ raku psu --min=2 --product Inf -Inf
[*:-Inf] -> ([Inf -Inf])

The third one is (also) correct, as Inf * 0 -> NaN.

Challenge #061.2: IPv4 Partition

You are given a string containing only digits (0..9). The string should have between 4 and 12 digits.

Write a script to print every possible valid IPv4 address that can be made by partitioning the input string.

For the purpose of this challenge, a valid IPv4 address consists of four “octets” i.e. A, B, C and D, separated by dots (.).

Each octet must be between 0 and 255, and must not have any leading zeroes. (e.g., 0 is OK, but 01 is not.)

Example

Input: 25525511135,

Output:

255.255.11.135
255.255.111.35

I'll start with validating the input. A leading zero (as in «0111» -> «0.1.1.1») is OK, and we can actually have four zeroes and nothing else («0000» -> «0.0.0.0»). This means that the input must be treated as a string, and not an integer (to avoid missing any leading zeroes).

subset IP-Decimal where 4 <= $_.chars <= 12 && all($_.comb) ~~ /<[0..9]>/;
 # [1] ########## # [2] ###################    # [3] ####################

[1] A custom type declared with subset. Note the missing of Str part that we can leave out as it wouldn't restrict the values.

[2] A where clause where we restrict the legal values. The first part checks the length of the string, by chaining to comparisons.

[3] And the second part uses an all Junction to check that all the individual characters of the string (which we get with comb) are indeed digits.

We can try it in REPL mode:

> subset IP-Decimal where 4 <= $_.chars <= 12 && all($_.comb) ~~ /<[0..9]>/;
(IP-Decimal)
					      
> "12" ~~ IP-Decimal
False

> "1211" ~~ IP-Decimal
True

> 1211 ~~ IP-Decimal
True

> "121a1" ~~ IP-Decimal
False

It works for both numbers (without quotes) and strings (with quotes).

We can simplify the second part by smartmatching against the Int type instead of using a Junction:

subset IP-Decimal where 4 <= $_.chars <= 12 && $_ ~~ Int;

Testing it with REPL:

> subset IP-Decimal where 4 <= $_.chars <= 12 && $_ ~~ Int;
(IP-Decimal)
					      
> "12" ~~ IP-Decimal
False

> "1211" ~~ IP-Decimal
False

> 1211 ~~ IP-Decimal
True

> "121a1" ~~ IP-Decimal
False

A string will not smartmatch with Int, even if the content is an integer. This is a problem in REPL, and not when we use a value from the command line, as we get a value of the IntStr type then. (As Raku sees the difference between numbers and strings by the presence of quotes. Values from the command line are without quotes, as the Shell removes them.)

See docs.raku.org/type/IntStr for more information about IntStr.

The documentation states that we get values of the IntStr if we quote them like this: <12>. Let us try:

> <211> ~~ IP-Decimal4
True

> <1211a> ~~ IP-Decimal4
False

But we can simplify the second part even further, by trying to coerce the value to an integer. (And I removed $_, as that is implied if we apply a method on nothing.)

subset IP-Decimal where 4 <= .chars <= 12 && .Int;

And this one works for strings as well:

> <1211a> ~~ IP-Decimal
False

> <1211> ~~ IP-Decimal
True

> <12aa11> ~~ IP-Decimal
False
	  
> "12aa11" ~~ IP-Decimal
False

> "1211" ~~ IP-Decimal
True

I have decided to treat this as en excercise in merging lists. We need a binary mask (yeah, not the right name for this, but I had to call it something) to insert beteen the digits in the input string:

The stars are either «0» or «1», and we substitute «1» with a dot and remove the «0» to get the (potential) IP address.

File: ipv4 (partial)
subset IP-Decimal where 4 <= .chars <= 12 && .Int;            # [1]

unit sub MAIN (IP-Decimal $ip-decimal, :$verbose);            # [1]

my @ipv4 = $ip-decimal.comb;                                  # [2]

my $length  = $ip-decimal.chars - 1;                          # [3]
my $decimal = 2 ** $length -1;                                # [4]

say ": Binary mask: { 1 x $length } - $decimal" if $verbose;  # [5]

[1] Use the custom type to validat the input.

[2] Get the individual digits (as a list).

[3] Get the length of the binary mask.

[4] Get the decimal value of the binary mask. See the illustration for explanation.

[5] Verbose output, showing that we got it right.

By iterating all the values from 1 up to the higest value for the mask, we get all the possible positions for the periods:

File: ipv4 (partial)
CANDIDATE: for 1 .. $decimal -> $current              # [6]
{
  my $val     = $current.fmt("%0" ~ $length ~ "b");   # [7]
  my $old-val = $val;                                 # [8] 

[6] Iterate over all the possible binary masks (as a decimal value).

[7] Get the binary value, padded with leading zeros.

[8] Don't worry abouth this one. It is used by verbose mode only.

Most of the candidates have the wrong number of dots (anything else than 3) and can be discarded easily:

File: ipv4 (partial)
  next unless $val.comb.sum == 3;  # [9]

[9] The binary number has «1» where the periods are (and «0» for the rest). We can simply count them.

Note that we could have started the loop at a higher value than 1, as we require 3 instances of «1», with at least one «0» between them. "10101".parse-base(2) gives us 21, which we could us a s the lower limit. But it doesn't really matter as the program is quick enough.

We convert the «0» in the mask to nothing (a space first, which we'll remove later, as we need a letter to get the correct number of elements in the list), and the «1» to «.» to get a candidate:

File: ipv4 (partial)
  $val ~~ tr/01/ ./;

Merging two lists with one value from each list (called zip merge) is easy in Raku, as it has a zip function (and a corresponding Z operator) built in. The problem is that it stops when the first list is empty, so the final value («8» in the illustration) will be lost. We could have added a trailing space to $val (in [5]; e.g. $current.fmt("%0" ~ $length ~ "b") ~ " "), but it is better to use roundrobin instead - as it goes on until both lists are empty:

> zip <1 2 3 4 5 6 7 8>, <x x x x x x x>
((1 x) (2 x) (3 x) (4 x) (5 x) (6 x) (7 x))

> <1 2 3 4 5 6 7 8> Z <x x x x x x x>
((1 x) (2 x) (3 x) (4 x) (5 x) (6 x) (7 x))

> roundrobin <1 2 3 4 5 6 7 8>, <x x x x x x x>
((1 x) (2 x) (3 x) (4 x) (5 x) (6 x) (7 x) (8))

Note that we get a list of Pair objects, and not really a flat list. But that is not a problem in practice.

See docs.raku.org/routine/roundrobin for more information about zip function.

See docs.raku.org/routine/zip for more information about zip function.

See docs.raku.org/routine/Z for more information about Z operator.

File: ipv4 (partial)
  my $ip = (roundrobin @ipv4, $val.comb).join.trans(' ' => '');  # [10]

  say ": Iterate: $ip-decimal | $current | $old-val ($val) -> \
    $ip: Found 4 parts" if $verbose;

[10] The join call flattens the list for us (see above), and the trans call removes the spaces in the resulting string.

File: ipv4 (the rest)
  my @elems = $ip.split(".");                               # [11]

  next if any(@elems) > 255;                                # [12]

  for 0 .. 3 -> $index                                      # [13]
  {
    next CANDIDATE if @elems[$index].Int ne @elems[$index]; # [14] 
  # next CANDIDATE if @elems[$index] > 255;                 # [15]
  }

  say @elems.join(".");                                     # [16]
}

[11] Get the four octets.

[12] Discared the candidate if any of the octets have a too high value.

[13] For each octet,

[14] • Discard the candidate if the octet has leading zeroes. (The Int call turns the value into an integer, removing any leading zeroes.)

[15] Use this unstead of [12] if you don't like Junctions.

[15] We have a legal IP address. Print it.

Running it:

$ raku ipv4 0000
0.0.0.0

$ raku ipv4 00000

$ raku ipv4 12345678
123.45.67.8
123.45.6.78
123.4.56.78
12.34.56.78
1.234.56.78

$ raku ipv4 --verbose 12345
: Binary mask: 1111 - 15
: Iterate: 12345 | 7 | 0111 ( ...) -> 12.3.4.5: Found 4 parts
12.3.4.5
: Iterate: 12345 | 11 | 1011 (. ..) -> 1.23.4.5: Found 4 parts
1.23.4.5
: Iterate: 12345 | 13 | 1101 (.. .) -> 1.2.34.5: Found 4 parts
1.2.34.5
: Iterate: 12345 | 14 | 1110 (... ) -> 1.2.3.45: Found 4 parts
1.2.3.45

And finally a longer example:

$ raku ipv4 123456789
123.45.67.89

$ raku ipv4 --verbose 123456789
: Binary mask: 11111111 - 255
: Iterate: 123456789 | 7 | 00000111 (     ...) -> 123456.7.8.9: Found 4 parts
: Iterate: 123456789 | 11 | 00001011 (    . ..) -> 12345.67.8.9: Found 4 parts
: Iterate: 123456789 | 13 | 00001101 (    .. .) -> 12345.6.78.9: Found 4 parts
: Iterate: 123456789 | 14 | 00001110 (    ... ) -> 12345.6.7.89: Found 4 parts
: Iterate: 123456789 | 19 | 00010011 (   .  ..) -> 1234.567.8.9: Found 4 parts
: Iterate: 123456789 | 21 | 00010101 (   . . .) -> 1234.56.78.9: Found 4 parts
: Iterate: 123456789 | 22 | 00010110 (   . .. ) -> 1234.56.7.89: Found 4 parts
: Iterate: 123456789 | 25 | 00011001 (   ..  .) -> 1234.5.678.9: Found 4 parts
: Iterate: 123456789 | 26 | 00011010 (   .. . ) -> 1234.5.67.89: Found 4 parts
: Iterate: 123456789 | 28 | 00011100 (   ...  ) -> 1234.5.6.789: Found 4 parts
: Iterate: 123456789 | 35 | 00100011 (  .   ..) -> 123.4567.8.9: Found 4 parts
: Iterate: 123456789 | 37 | 00100101 (  .  . .) -> 123.456.78.9: Found 4 parts
: Iterate: 123456789 | 38 | 00100110 (  .  .. ) -> 123.456.7.89: Found 4 parts
: Iterate: 123456789 | 41 | 00101001 (  . .  .) -> 123.45.678.9: Found 4 parts
: Iterate: 123456789 | 42 | 00101010 (  . . . ) -> 123.45.67.89: Found 4 parts
123.45.67.89
: Iterate: 123456789 | 44 | 00101100 (  . ..  ) -> 123.45.6.789: Found 4 parts
: Iterate: 123456789 | 49 | 00110001 (  ..   .) -> 123.4.5678.9: Found 4 parts
: Iterate: 123456789 | 50 | 00110010 (  ..  . ) -> 123.4.567.89: Found 4 parts
: Iterate: 123456789 | 52 | 00110100 (  .. .  ) -> 123.4.56.789: Found 4 parts
: Iterate: 123456789 | 56 | 00111000 (  ...   ) -> 123.4.5.6789: Found 4 parts
: Iterate: 123456789 | 67 | 01000011 ( .    ..) -> 12.34567.8.9: Found 4 parts
: Iterate: 123456789 | 69 | 01000101 ( .   . .) -> 12.3456.78.9: Found 4 parts
: Iterate: 123456789 | 70 | 01000110 ( .   .. ) -> 12.3456.7.89: Found 4 parts
: Iterate: 123456789 | 73 | 01001001 ( .  .  .) -> 12.345.678.9: Found 4 parts
: Iterate: 123456789 | 74 | 01001010 ( .  . . ) -> 12.345.67.89: Found 4 parts
: Iterate: 123456789 | 76 | 01001100 ( .  ..  ) -> 12.345.6.789: Found 4 parts
: Iterate: 123456789 | 81 | 01010001 ( . .   .) -> 12.34.5678.9: Found 4 parts
: Iterate: 123456789 | 82 | 01010010 ( . .  . ) -> 12.34.567.89: Found 4 parts
: Iterate: 123456789 | 84 | 01010100 ( . . .  ) -> 12.34.56.789: Found 4 parts
: Iterate: 123456789 | 88 | 01011000 ( . ..   ) -> 12.34.5.6789: Found 4 parts
: Iterate: 123456789 | 97 | 01100001 ( ..    .) -> 12.3.45678.9: Found 4 parts
: Iterate: 123456789 | 98 | 01100010 ( ..   . ) -> 12.3.4567.89: Found 4 parts
: Iterate: 123456789 | 100 | 01100100 ( ..  .  ) -> 12.3.456.789: Found 4 parts
: Iterate: 123456789 | 104 | 01101000 ( .. .   ) -> 12.3.45.6789: Found 4 parts
: Iterate: 123456789 | 112 | 01110000 ( ...    ) -> 12.3.4.56789: Found 4 parts
: Iterate: 123456789 | 131 | 10000011 (.     ..) -> 1.234567.8.9: Found 4 parts
: Iterate: 123456789 | 133 | 10000101 (.    . .) -> 1.23456.78.9: Found 4 parts
: Iterate: 123456789 | 134 | 10000110 (.    .. ) -> 1.23456.7.89: Found 4 parts
: Iterate: 123456789 | 137 | 10001001 (.   .  .) -> 1.2345.678.9: Found 4 parts
: Iterate: 123456789 | 138 | 10001010 (.   . . ) -> 1.2345.67.89: Found 4 parts
: Iterate: 123456789 | 140 | 10001100 (.   ..  ) -> 1.2345.6.789: Found 4 parts
: Iterate: 123456789 | 145 | 10010001 (.  .   .) -> 1.234.5678.9: Found 4 parts
: Iterate: 123456789 | 146 | 10010010 (.  .  . ) -> 1.234.567.89: Found 4 parts
: Iterate: 123456789 | 148 | 10010100 (.  . .  ) -> 1.234.56.789: Found 4 parts
: Iterate: 123456789 | 152 | 10011000 (.  ..   ) -> 1.234.5.6789: Found 4 parts
: Iterate: 123456789 | 161 | 10100001 (. .    .) -> 1.23.45678.9: Found 4 parts
: Iterate: 123456789 | 162 | 10100010 (. .   . ) -> 1.23.4567.89: Found 4 parts
: Iterate: 123456789 | 164 | 10100100 (. .  .  ) -> 1.23.456.789: Found 4 parts
: Iterate: 123456789 | 168 | 10101000 (. . .   ) -> 1.23.45.6789: Found 4 parts
: Iterate: 123456789 | 176 | 10110000 (. ..    ) -> 1.23.4.56789: Found 4 parts
: Iterate: 123456789 | 193 | 11000001 (..     .) -> 1.2.345678.9: Found 4 parts
: Iterate: 123456789 | 194 | 11000010 (..    . ) -> 1.2.34567.89: Found 4 parts
: Iterate: 123456789 | 196 | 11000100 (..   .  ) -> 1.2.3456.789: Found 4 parts
: Iterate: 123456789 | 200 | 11001000 (..  .   ) -> 1.2.345.6789: Found 4 parts
: Iterate: 123456789 | 208 | 11010000 (.. .    ) -> 1.2.34.56789: Found 4 parts
: Iterate: 123456789 | 224 | 11100000 (...     ) -> 1.2.3.456789: Found 4 parts

And that's it.