Mask the MAC
with Raku

by Arne Sommer

Mask the MAC with Raku

[205] Published 9. October 2022.

This is my response to The Weekly Challenge #185.

Challenge #185.1: MAC Address

You are given MAC address in the form i.e. hhhh.hhhh.hhhh.

Write a script to convert the address in the form hh:hh:hh:hh:hh:hh.

Example 1:
Input:  1ac2.34f0.b1c2
Output: 1a:c2:34:f0:b1:c2
Example 2:
Input:  abc1.20f1.345a
Output: ab:c1:20:f1:34:5a

Let us do the reverse operation as well.

File: mac-address
#! /usr/bin/env raku

multi sub MAIN ($mac-dot
  where /^ <[0..9a..f]> **4 . <[0..9a..f]> **4 . <[0..9a..f]> **4 $/)    # [1]
{
  say $mac-dot.split(".")>>.map({ $_.substr(0,2) ~ ":" ~ $_.substr(2,2) }).join(":");
    # # 2a ############# # 2b ############################################ # 2c ####
}

multi sub MAIN ($mac-colon
  where /^ <[0..9a..f]> **2 \: <[0..9a..f]> **2 \: <[0..9a..f]> **2 
	\: <[0..9a..f]> **2 \: <[0..9a..f]> **2 \: <[0..9a..f]> **2 $/)  # [3]
{
  say $mac-colon.split(":").rotor(2)>>.join.join(",");
    # # 4a ################ # 4b ## # 4c ## # 4d ####
		      }

Multiple dispatch (with multi and a when clause on the single argument), is used to decide which operation to perform.

[1] The first regex matches four (the **4 quantifier) hexadecimal digits (lowercase letters only) (the <[0..9a..f]> regex), followed by a period (the literal .), another four hexadecimal chadigits, a second period and finally another four hexadecimal digits.

[2] Start with the string, and split it on the periods. This gives us a list of strings (3 of them, each consisting of 4 digits) [2a]. Then we use map on each of those strings (with the >>.) to include a colon in the middle [2b]. And finally we join the strings (3 of them) with a colon between each one [2c].

[3] This version matches 6 instances of two hexadecimal digits digits, with a colon betwean each one. Note that we had to escape the colon.

[4] Start with a list of 6 strings, each consisting of two hexadecimal digits [4a]. Use rotor(2) to group two and two elements in sublists, giving a list with three elemens. Each element is again a list with two elements [4b]. Then we use >>.join to join the elements of the sublist into a string, thus giving a list with three string elements [4c]. And finally we join those strings together with a period between each one [4d].

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

Running it:

$ ./mac-address 1ac2.34f0.b1c2
1a:c2:34:f0:b1:c2

$ ./mac-address 1a:c2:34:f0:b1:c2
1ac2.34f0.b1c2

$ ./mac-address abc1.20f1.345a
ab:c1:20:f1:34:5a

$ ./mac-address ab:c1:20:f1:34:5a
abc1.20f1.345a

Looking good.

Challenge #185.2: Mask Code

You are given a list of codes in many random format.

Write a script to mask first four characters (a-z,0-9) and keep the rest as it is.

Example 1:
Input: @list = ('ab-cde-123', '123.abc.420', '3abc-0010.xy')
Output: ('xx-xxe-123', 'xxx.xbc.420', 'xxxx-0010.xy')
Example 2:
Input: @list = ('1234567.a', 'a-1234-bc', 'a.b.c.d.e.f')
Output: ('xxxx567.a', 'x-xxx4-bc', 'x.x.x.x.e.f')

I have chosen to hard code the lists this time.

File: mask-code
#! /usr/bin/env raku

my regex a9 { <[0..9 a..z]> };    # [1]

my @list1 = ('ab-cde-123', '123.abc.420', '3abc-0010.xy');
my @list2 = ('1234567.a',  'a-1234-bc',   'a.b.c.d.e.f');

say mask-code(@list1);
say mask-code(@list2);

sub mask-code (@list)
{
  my @return;

  for @list -> $string is copy
  {
    my $count = 0;                               # [2]

    for ^$string.chars -> $index                 # [3]
    {
      # say ": $string + $index -> { $string.substr($index,1) } C:$count";

      if $string.substr($index,1) ~~ /^ <a9> $/  # [4]
      {
        $string.substr-rw($index,1) = "x";       # [5]
        $count++;                                # [2a]
	last if $count == 4;                     # [2b]
      }
    }
    @return.push: $string;
  }

  return @return;
}

[1] We set up a named regex (also called a subrule) to catch one alphanumeric character (lowercase only). It is used in [4].

See docs.raku.org/language/regexes#index-entry-declarator_regex-Subrules for more information about the regex keyword.

[2] The number of characters we have replaced, starting at none. Incremented after each replacement [2a] and we stop when we have reached 4 replacements [2b].

[3] Iterate over the indices of the current string.

[4] Is the character with the current index an alphanumeric character?

[5] If yes, replace it with the character «x». Note the use of substr-rw, as we are changing the value, instead of substr which is read only. Note the is copy in the outermost for loop, so that we actually can change the value.

See docs.raku.org/routine/substr-rw for more information about substr-rw.

Uncomment the commented out line, if you want some verbose output.

Running it:

$ ./mask-code 
[xx-xxe-123 xxx.xbc.420 xxxx-0010.xy]
[xxxx567.a x-xxx4-bc x.x.x.x.e.f]

Looking good. (Except the formatting; brackets instead of parens, space instead of commas, and the missing quotes.)

And that's it.