Sequenced Split
with Raku and Perl

by Arne Sommer

Sequenced Split with Raku and Perl

[204] Published 2. October 2022.

This is my response to The Weekly Challenge #184.

Challenge #184.1: Sequence Number

You are given list of strings in the format aa9999 i.e. first 2 characters can be anything 'a-z' followed by 4 digits '0-9'.

Write a script to replace the first two characters with sequence starting with '00', '01', '02' etc.

Example 1:
Input: @list = ( 'ab1234', 'cd5678', 'ef1342')
Output: ('001234', '015678', '021342')
Example 2:
Input: @list = ( 'pq1122', 'rs3334')
Output: ('001122', '013334')

I have chosen to hard code the examples.

File: sequence-number
#! /usr/bin/env raku

my @list1 = ('ab1234', 'cd5678', 'ef1342');                  # [1]
my @list2 = ('pq1122', 'rs3334');                            # [1]

say sequence-number(@list1);                                 # [2]
say sequence-number(@list2);                                 # [2]

sub sequence-number (@list)                                  # [2a]
{
  my @return;

  for @list -> $string                                       # [3]
  {
    state $count = 0;                                        # [4]

    @return.push($count.fmt('%02d') ~ $string.substr(2,4));  # [5]
    $count++;                                                # [4a]
  }

  return @return;                                            # [5]
}

[1] The two example arrays.

[2] Print the result of transforming the arrays.

[3] Iterate over the strings in the input array.

[4] I have chosen to use a state variable for the the counter. State variables are initialized only once (or initially, as it were), and retains the (previous) value thereafter.

See docs.raku.org/syntax/state for more information about the variable declarator state.

[5] Start with the counter, as a two-digit number (courtesy of fmt, the method form of sprintf) and append the four last characters from the input string (courtesy of substr).

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

Running it:

$ ./sequence-number
[001234 015678 021342]
[001122 013334]

Looking good. Except the brackets instead of parens and the missing quotes. One can argue that this is a result of the stringification caused by the printing, and that the Raku data structure adheres to the output. So I'll leave it here.

A Perl Version

This is straight forward translation of the Raku version, except for the manual stringification.

File: sequence-number-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use v5.20;

use feature 'signatures';
no warnings 'experimental::signatures';

my @list1 = ('ab1234', 'cd5678', 'ef1342');
my @list2 = ('pq1122', 'rs3334');

say sequence_number(@list1);
say sequence_number(@list2);

sub sequence_number (@list)
{
  my @return;

  for my $string (@list)
  {
    state $count = 0;

    push(@return, sprintf('%02d', $count) . substr($string, 2, 4));
    $count++;
  }

  return "(" . join(", ", map { "'$_'"} @return) . ")";  # [1]
}

[1] Printing arrays does not work out that well in Perl, so we stringify them manually. (See the discussion at the end of the perl part of «Split Array» for some more thoughts about the matter.

Running it gives the expected result.

$ ./sequence-number-perl
('001234', '015678', '021342')
('031122', '043334')

Challenge #184.2: Split Array

You are given list of strings containing 0-9 and a-z separated by space only.

Write a script to split the data into two arrays, one for integers and one for alphabets only.

Example 1:
Input: @list = ( 'a 1 2 b 0', '3 c 4 d')
Output: [[1,2,0], [3,4]] and [['a','b'], ['c','d']]
Example 2:
Input: @list = ( '1 2', 'p q r', 's 3', '4 5 t')
Output: [[1,2], [3], [4,5]] and [['p','q','r'], ['s'], ['t']]

File: split-array
#! /usr/bin/env raku

my @list1 = ('a 1 2 b 0', '3 c 4 d');             # [1]
my @list2 = ('1 2', 'p q r', 's 3', '4 5 t');     # [2]

say split-array(@list1);                          # [3]
say split-array(@list2);                          # [3]

sub split-array (@list)                           # [3a]
{
  my @digits;                                     # [4]
  my @letters;                                    # [5]

  for @list -> $string                            # [6]
  {
    my @d-curr;                                   # [7]
    my @l-curr;                                   # [8]

    for $string.words -> $char                    # [9]
    {
      $char eq any(0..9) ?? @d-curr.push($char) !! @l-curr.push($char);
    }                                             # [10]

    @digits.push: @d-curr  if @d-curr;            # [11]
    @letters.push: @l-curr if @l-curr;            # [12]
  }

  return (@digits, @letters);                     # [13]
}

[1] The values from example 1.

[2] The values from example 2.

[3] Print the new values, given by the «split-array» procedure ([3a]).

[4] We are going to collect the arrays of digits here.

[5] Ditto for the letters.

[6] Iterate over the input strings.

[7] The digits in the current input string will end up here.

[8] Ditto for the letters.

[9] Iterate over the individual characters in the string.

[10] Add the character to the correct array; letter or digit.

[11] Add the current list of digits to the global list of digits, if any.

[12] Ditto for the letters

[13] Return the list of list of digits, and the list of list of letters.

Running it:

$ ./split-array
([[1 2 0] [3 4]] [[a b] [c d]])
([[1 2] [3] [4 5]] [[p q r] [s] [t]])

Looking good. Except the extra set of parens and the missing commas and quotes. Let us argue, once again, that this is a feature of the stringifiaction process - and not a property of the internal data structure.

Perl

This is a straight forward translation of the Raku version, except that we have to stringify the data structure manually (as printing an array gives a result like e.g. ARRAY(0x559b775cf1b0)).

File: split-array-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use feature 'signatures';

no warnings 'experimental::signatures';

use Perl6::Junction 'any';

my @list1 = ('a 1 2 b 0', '3 c 4 d');
my @list2 = ('1 2', 'p q r', 's 3', '4 5 t');

say split_array(@list1);
say split_array(@list2);

sub split_array (@list)
{
  my @digits;
  my @letters;

  for my $string (@list)
  {
    my @d_curr;
    my @l_curr;

    for my $char (split(" ", $string))
    {
      $char eq any(0..9) ? push(@d_curr, $char) : push(@l_curr, $char);
    }

    push(@digits,  "[" . join(",", @d_curr) . "]") if @d_curr;             # [1]
    push(@letters, "[" . join(",", map { "'" . $_ . "'" } @l_curr) . "]")  # [1]
      if @l_curr;
  }

  my @return;

  push(@return, "[" . join (", ", @digits)  . "]") if @digits;             # [1]
  push(@return, "[" . join (", ", @letters) . "]") if @letters;            # [1]

  return join(" and ", @return);                                           # [2]
}

[1] Note that we push a complete string with brackets commas and quotes (in the case of letters) instead of an array.

[2] The icing on the cake is the addition of the word «and» between the two arrays. I do not really think that we should take the output of the examples literally, but it sure is fun to do so anyway...

Running it gives the same values as the Raku version, but with the commas and quoted letters in place:

$ ./split-array-perl
[[1,2,0],[3,4]] and [['a','b'],['c','d']]
[[1,2], [3], [4,5]] and [['p','q','r'], ['s'], ['t']]

Fun Fact: Anagrams

«Split Array» has several anagrams. Three of them sort of make sense:
  • Try a spiral
  • Salary trip
  • Spiral tray

See United States of Anagrams with Raku - Part 3: Multigrams for how to create multi word anagrams.

And that's it.