Linked Roots
with Raku

by Arne Sommer

Linked Roots with Raku

[145] Published 12. September 2021.

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

Challenge #129.1: Root Distance

You are given a tree and a node of the given tree.

Write a script to find out the distance of the given node from the root.

Example 1:
Tree:
        1
       / \
      2   3
           \
            4
           / \
          5   6

Node: 6
Output: 3 as the distance of given node 6 from the root (1).

Node: 5
Output: 3

Node: 2
Output: 1

Node: 4
Output: 2
Example 2:
Tree:
        1
       / \
      2   3
     /     \
    4       5
     \     /
      6   7
     / \
    8   9

Node: 7
Output: 3 as the distance of given node 6 from the root (1).

Node: 8
Output: 4

Node: 6
Output: 3

The tree looks like a binary tree (where each node can have 0, 1 or 2 child nodes), but the challenge does not say so. Let us assume that it indeed is a binary tree (given the definition above). Then we can (re)use quite a lot of the code from the second part of challenge 125 Pythagorean Tree with Raku (Binary Tree Diameter).

The type of the node values is not described, but the values used in the examples are single digit positive integers. But I'll refrain from adding a restraint on the values.

The tree looks kind of sorted and there are no duplicates, but this is also just an observation. In case of duplicate matches, do we report one (and which one), or all? I'll get back to this conundrum.

The first part of the program (shown below) is a copy of the program «binary-tree-diameter» (from challenge 125; see above - and follow link for the explanation, if needed), except for the default tree and the extra node parameter:

File: root-distance (partial)
#! /usr/bin/env raku

unit sub MAIN (Str $tree = "1 | 2 3 | * * * 4  | 5 6",
               Str $node = '6',
               :v(:$verbose));

class BinaryNode
{
  has Int        $.value;
  has BinaryNode $.left;
  has BinaryNode $.right;
}

my @btree = $tree.split("|")>>.words;

my @old-nodes;
my @new-nodes;

for @btree.reverse -> $row
{
  my @current = @$row;
  @old-nodes  = @new-nodes;
  @new-nodes  = ();
  
  for @current -> $value
  {
    if $value eq "*"
    {
      @new-nodes.push("*");
      next;
    }

    my $left  = @old-nodes.shift // "*"; $left  = Nil if $left  eq "*";
    my $right = @old-nodes.shift // "*"; $right = Nil if $right eq "*";

    @new-nodes.push(BinaryNode.new(value  => $value.Int,
                                   left   => $left  // Nil,
                                   right  => $right // Nil)); 
  }
}

my $btree = @new-nodes[0];

The recursive traversing procedure does the same traversal (as in challenge 125), but what it does on each node has been changed somewhat:

File: root-distance (the rest)
my $distance = -1;

traverse2($btree, $node, ());

say $distance;

sub traverse2 ($current, $target, @path is copy)
{
  @path.push: $current.value;

  if $current.value == $target                         # [1]
  {
    say ": Path: { @path.join(" > ") }" if $verbose;
    $distance = @path.elems -1;                        # [2]
    return;                                            # [3]
  }
  
  if ($current.left || $current.right)
  {
    traverse2($current.left,  $target, @path) if $current.left;
    traverse2($current.right, $target, @path) if $current.right;
  }
}

[1] Have we found the value we are looking for?.

[2] • Set the distance,

[3] • Return from the recursive call(s).

Note that the recursion may cause traversal to continue after we have found the node.

Running it:

$ ./root-distance 
3

$ ./root-distance "1 | 2 3 | * * * 4 | 5 6" 6
3

$ ./root-distance "1 | 2 3 | * * * 4 | 5 6" 5
3

$ ./root-distance "1 | 2 3 | * * * 4 | 5 6" 2
1

$ ./root-distance "1 | 2 3 | * * * 4 | 5 6" 4
2

$ ./root-distance "1 | 2 3 | 4 * * 5 | * 6 7 | 8 9" 7
3

$ ./root-distance "1 | 2 3 | 4 * * 5 | * 6 7 | 8 9" 8
4

$ ./root-distance "1 | 2 3 | 4 * * 5 | * 6 7 | 8 9" 6
3

Looking good.

With verbose mode:

$ ./root-distance -v "1 | 2 3 | * * * 4 | 5 6" 6
: Path: 1 > 3 > 4 > 6
3

$ ./root-distance -v "1 | 2 3 | * * * 4 | 5 6" 5
: Path: 1 > 3 > 4 > 5
3

$ ./root-distance -v "1 | 2 3 | * * * 4 | 5 6" 2
: Path: 1 > 2
1

$ ./root-distance -v "1 | 2 3 | * * * 4 | 5 6" 4
: Path: 1 > 3 > 4
2

$ ./root-distance -v "1 | 2 3 | 4 * * 5 | * 6 7 | 8 9" 7
: Path: 1 > 3 > 5 > 7
3

$ ./root-distance -v "1 | 2 3 | 4 * * 5 | * 6 7 | 8 9" 8
: Path: 1 > 2 > 4 > 6 > 8
4

$ ./root-distance -v "1 | 2 3 | 4 * * 5 | * 6 7 | 8 9" 6
: Path: 1 > 2 > 4 > 6
3

The program will only print one match, which is ok if we assume that the values are distinct. Adding support for duplicates is easy:

File: root-distance-all (changes only)
unit sub MAIN (Str $tree = "1 | 2 3 | * * * 4  | 5 6",
               Str $node = '6',
               :a(:$all),            # [1]
               :v(:$verbose));
my $btree = @new-nodes[0];

my @distance;                        # [2]

traverse2($btree, $node, ());

say @distance.join(" ");             # [3]

sub traverse2 ($current, $target, @path is copy)
{
  @path.push: $current.value;

  if $current.value == $target
  {
    say ": Path: { @path.join(" > ") }" if $verbose;
    @distance.push(@path.elems -1);  # [4]
    return unless $all;
  }
  
  if ($current.left || $current.right)
  {
    traverse2($current.left,  $target, @path) if $current.left;
    traverse2($current.right, $target, @path) if $current.right;
  }
}

[1] Enable «all» mode with this command line option.

[2] We are going to collect the distances here.

[3] Print the disnace - or distances.

[4] We have a match; add it to the list.

[4] We are finished, unless we want them all.

Running it:

$ ./root-distance-all "1 | 2 3 | 4 * 5 6 | 6" 6
3

$ ./root-distance-all -v "1 | 2 3 | 4 * 5 6 | 6" 6
: Path: 1 > 2 > 4 > 6
3

$ ./root-distance-all -v -a "1 | 2 3 | 4 * 5 6 | 6" 6
: Path: 1 > 2 > 4 > 6
: Path: 1 > 3 > 6
3 2

The program does a depth first search, so it reports a match with a longer distance first. This feature does not apply to the original program:

$ ./root-distance -v "1 | 2 3 | 4 * 5 6 | 6" 6
: Path: 1 > 2 > 4 > 6
: Path: 1 > 3 > 6
2

The fact that it goes on after finding a match is also a feature. Let us try mirroring the tree:

$ ./root-distance -v "1 | 3 2 | 6 5 * 4 | * * * * * 6" 6
: Path: 1 > 3 > 6
: Path: 1 > 2 > 4 > 6
3

Now it found them in reversed order, and the one with the longest distance won.

Conclusion: Duplicate values is unchartered territory, so we can argue that returning a random one is ok (albeit the same one each time to placate entropy haters). Returning all of them is also ok. As is the one with the shortest path. So, this isn't really much of a conclusion.

Challenge #129.2: Add Linked Lists

You are given two linked list having single digit positive numbers.

Write a script to add the two linked list and create a new linked representing the sum of the two linked list numbers. The two linked lists may or may not have the same number of elements.

HINT: Just a suggestion, feel free to come up with your own unique way to deal with the task. I am expecting a class representing linked list. It should have methods to create a linked list given list of single digit positive numbers and a method to add new member. Also have a method that takes 2 linked list objects and returns a new linked list. Finally a method to print the linked list object in a user friendly format.

Example 1:
Input: L1 = 1 -> 2 -> 3
       L2 = 3 -> 2 -> 1
Output: 4 -> 4 -> 4

    Operation: Pick the first rightmost element of L1 i.e. 3 and adds to
    the first rightmost element of L2 i.e. 1. Finally store the result
    i.e. 3 in the new linked list. Move to the next one of both linked
    lists L1 and L2, perform the same operation. In case the sum >= 10
    then you apply the same rule you would do to regular addition problem
    i.e. divide the sum by 10 keep the remainder and push to the new
    linked list. Don't forget to carry, 1, to the next operation.
    In case one linked list is smaller than the other, you can safely
    assume it is 0..
Example 2:
Input: L1 = 1 -> 2 -> 3 -> 4 -> 5
       L2 =           6 -> 5 -> 5
Output:     1 -> 3 -> 0 -> 0 -> 0

    Operation:
    a) 1st member of L1 = 5 and 1st member of L2 = 5
    b) 5 + 5 = 10
    c) 0 pushed to the new linked list.
    d) carry forward 1.
    e) 2nd member of L1 = 4 and 2nd member of L2 = 5
    f) 4 + 5 + 1 (carry) = 10
    h) 0 again pushed to the new linked list.
    i) carry forward 1.
    j) 3rd member of L1 = 3 and 3rd member of L2 = 6
    k) 3 + 6 + 1 (carry) = 10
    l) 0 pshed to the new linked list.
    m) carry forward 1.
    n) 4th member of L1 = 2 and assume 0 as the 4th member of L2
       since there are only 3 members.
    o) 2 + 1 (carry) = 3
    p) 3 pushed to the new linked list.
    q) 5th member of L1 = 1 and assume 0 as the 5th member of L2
       since there are only 3 members.
    r) 1 + 0 = 1
    s) 1 pushed to the new linked list.

    So the new linked list now have: 1 -> 3 -> 0 -> 0 -> 0.

    The above suggestion is one way, not necessarily the best way
    to deal with it.

Let us start with the class, and a program that generates the linked list given on the command line:

File: linked-list-sub
#! /usr/bin/env raku

unit sub MAIN ($values = "1 2 3 4");                         # [1]

my @values = $values.words;                                  # [2]

die "Must be 1..9 only" unless all(@values) ~~ /^<[1..9]>$/; # [3]

class LinkedList                                             # [4]
{
  has Int        $.value;                                    # [4a]
  has LinkedList $.next is rw;                               # [4b]
}

sub create_linked_list (@values is copy)                     # [5]
{
  my $value   = @values.shift;                               # [6]
  my $top     = LinkedList.new(value => $value);             # [7]
  my $current = $top;                                        # [8]

  for @values -> $value                                      # [9]
  {
    $current.next = LinkedList.new(value => $value);         # [9a]
    $current      = $current.next;                           # [9b]
  }

  return $top;                                               # [10]
}

my $ll = create_linked_list(@values>>.Int);                  # [11]

say $ll.raku;                                                # [12]

[1] Specify the values in a space separated string. (This is in preparation for the full program, where we need two of them.)

[2] Get the individual values.

[3] The challenge specified «single digit positive numbers», which excludes zero. That is probably an error, but let us adhere to this requirement (for now). The program uses an all junction to check that all the values satisfy the regex.

[4] The class is very compact. Just two variables and no methods (for now). Note the use of is rw on the pointer to the next element in the list. That makes it possible to change the value after we have created the object.

[5] Procedure to create a linked lits, given a list of values.

[6] Get the first value.

[7] Create the top node (the first one in the linked list). this is the future return value (see [10]).

[8] This is a pointer to the currently last element in the list, as we go along adding values.

[9] For each additional value, add a new node to the end of the list [9a], and move the pointer one position outwards [9b].

[10] Return the list (the first element).

[11] Coerce the values to integers (as the constructor expects an integer), and off we go. Note that the check in [3] has verified that the strings can be coerced to integers without causing havoc (or truncating them).

[12] Print the linked list, using the internal representation.

See docs.raku.org/routine/all for more information about the all Junction.

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

Illegal values/characters are caught:

$ ./linked-list-sub "1 9 9 2 8 a"
Must be 1..9 only
…

$ ./linked-list-sub "1 9 9 2 8 0"
Must be 1..9 only
…

$ ./linked-list-sub "1 9 9 2 8 10"
Must be 1..9 only
…

Running it with legal values. Newlines and indentation has been added for clarity:

$ ./linked-list-sub "1 2 3"
LinkedList.new(
   value => 1,
   next  => LinkedList.new(
       value => 2,
       next  => LinkedList.new(
           value => 3,
           next  => LinkedList
       )
   )
)             

Let us try something better (clarity wise); the output format requested by the challenge: «1 -> 2 -> 3» - as a method.

Let us add support for zero as an input value at the same time, enabled by a command line option.

File: linked-list-sub2
#! /usr/bin/env raku

unit sub MAIN ($values = "1 2 3 4", :z(:$zero));  # [1]

my @values = $values.words;

$zero                                             # [1a]
  ?? ( die "Must be 0..9 only" unless all(@values) ~~ /^<[0..9]>$/ )
  !! ( die "Must be 1..9 only" unless all(@values) ~~ /^<[1..9]>$/ );

class LinkedList
{
  has Int        $.value;
  has LinkedList $.next is rw;

  method recursive-value
  {
    return flat(self.value, self.next.recursive-value) if self.next;  # [2]
    return self.value;                                                # [2a]
  }
}

sub create_linked_list (*@values is copy)
{
  my $value = @values.shift;
  my $top = LinkedList.new(value => $value);
  my $current = $top;

  for @values -> $value
  {
    $current.next = LinkedList.new(value => $value);
    $current      = $current.next;
  }

  return $top;
}

my $ll = create_linked_list(@values>>.Int);

say $ll.recursive-value.join(" -> ");  # [3]

[1] Enable support for zeroes with the «-z» command line option. I have places the validation outside of the class on purpose, so that we can use it in non-digit situastions as well.

[2] The method is recursive. Call it on any element in a linked list, and it will give you the values from there to the end (as a list). If there is a next node, it will return the current node value and recursively call istelf on the next node to get the rest. If this is the end, return the value [2a]. Note the use of flat to avoid recursive lists of lists.

[3] We got a list. Join the elements to form the required string.

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

Running it:

$ ./linked-list-sub2 
1 -> 2 -> 3 -> 4

$ ./linked-list-sub2 "1 9 9 2 8"
1 -> 9 -> 9 -> 2 -> 8

Why use a procedure to generate the linked list?

Why indeed. The challenge asked for a method (as a suggestion, mind you) and we should do just that:

File: linked-list-method
#! /usr/bin/env raku

unit sub MAIN ($values = "1 2 3 4", :z(:$zero));

my @values = $values.words;

$zero
  ?? ( die "Must be 0..9 only" unless all(@values) ~~ /^<[0..9]>$/ )
  !! ( die "Must be 1..9 only" unless all(@values) ~~ /^<[1..9]>$/ );

class LinkedList
{
  has Int        $.value;
  has LinkedList $.next is rw;

  method add-elems (*@values is copy)                 # [1]
  {
    my $list  = self;                                 # [2]
    my $first = @values.shift;                        # [3]
    my $obj   = LinkedList.new(value => $first);      # [4]

    ($list ~~ LinkedList:D)                           # [5]
      ?? ( $list.next = $obj )                        # [5a]
      !! ( $list      = $obj );                       # [5b]

    $obj.add-elems(@values) if @values.elems;         # [6]

    return $obj;                                      # [7]
  }

  method recursive-value
  {
    return flat(self.value, self.next.recursive-value) if self.next;
    return self.value;
  }
}

my $ll = LinkedList.add-elems(@values>>.Int);

say $ll.recursive-value.join(" -> ");

[1] Note the slurpy * prexif. This makes it possible to call the method with a comma separated list of values - as well as the ordinary list.

[2] Get the object we were called on.

[3] Get the first value.

[4] Create a new object with the value from [3].

[5] Adding the type constraint :D (defined) to the type of a parameter means that it must be defined. In a class setting, that means calling the method on an instance, e.g. $l1.add-elems(...). Calling it on the class itself, e.g. LinkedList.add-elems(...), satisfies the :U (undefined) type constraint. They are mutually exclusive. The point here is that calling «add-elems» on an existing object will add the values to it, and calling it on the class itself will give a new list (acting as a constructor).

[6] Recursively call ouselves if there are more values to add.

[7] Return the pointer to the first object we created.

See docs.raku.org/type/Signature#index-entry-type_constraint_:D for more information about the :D :U type constraints.

Replacements Я Us

Note that «add-elems» will replace an existing child element, if any. That is ok here (as we call it on the currently last node as we generate the list), but it is not what the challenge wanted.

The add-elems method can either be called on the type object, giving us a new list:

my $list1 = LinkedList.add-elems(1);            # -> (1)
my $list2 = LinkedList.add-elems(1, 2, 3, 4);   # -> (1 -> 2 -> 3 -> 4)

Or an existing LinkedList object, where it adds the value(s) to the existing list.

my $list3 = $list2.add-elems(5);                # -> (1 -> 5) ### Oops

Replacing the second value in the list (and getting rid of the rest of it) is probably not what you would expect. Or want. We can fix that.

We do not actually need is copy on the values, as we can use array slices instead.

We should make a module of it, so we can stop copying the code all the time.

The module (called «LinkedList1», as there will be more versions of it):

File: lib/LinkedList1.rakumod
class LinkedList
{
  has Int         $.value;
  has LinkedList1 $.next is rw;
  
  method add-elems (@values, :r(:$replace-next))                     # [1]
  {
    my $list  = self;
    my $first = @values[0];
    my $obj   = LinkedList1.new(value => $first);

    if $list ~~ LinkedList1:D
    {
      unless $replace-next { $list = $list.next while $list.next; }  # [2]
      $list.next = $obj;                                             # [3]
    }
    else
    {
      $list = $obj;
    }

    $obj.add-elems(@values[1 .. *-1]) if @values.elems > 1;

    return $obj;
  }

  method recursive-value
  {
    return flat(self.value, self.next.recursive-value) if self.next;
    return self.value;
  }

  method nice (Str $sep = ' => ')                                   # [4]
  {
    return self.recursive-value.join($sep);                         # [4a]
  }
}

[1] Get the old (replacement) functionality with the «:r» flag. If not, add to the end of the list

[2] If replacement, work on the current node. If not, get to the last node in the list.

[3] Add the new element to the end.

[4] Nice way of getting nice output. Note the support for a custom string between the values.

The program:

File: linked-list-module
#! /usr/bin/env raku

use lib "lib";    # [6]
use LinkedList1;  # [7]

unit sub MAIN ($values = "1 2 3 4", :z(:$zero));

my @values = $values.words;

$zero
  ?? ( die "Must be 0..9 only" unless all(@values) ~~ /^<[0..9]>$/ )
  !! ( die "Must be 1..9 only" unless all(@values) ~~ /^<[1..9]>$/ );

my $ll = LinkedList1.add-elems(@values>>.Int);

say $ll.nice;    # [8]

[6] I have placed the module file in the «lib» directory. Tell Raku to look there.

[7] Use the module.

[8] Much shorter (and nicer) than before…

Then we can finally have a go a the program doing what the challenge asked for; two linked lists as input, and the sum of them as output.

Note that the challenge basically wants us to reinvent (or reimplement) integer addition. We do not really have to do that.

File: add-linked-lists
#! /usr/bin/env raku

use lib "lib"; 
use LinkedList1;

unit sub MAIN (Str $string1 = '1 2 3',                      # [1]
               Str $string2 = '3 2 1',
                   :z(:$zero),
                   :v(:$verbose));

my @values1 = $string1.words;                               # [2]
my @values2 = $string2.words;                               # [2]

$zero                                                       # [3]
  ?? ( die "Must be 0..9 only"
         unless all((@values1, @values2).flat) ~~ /^<[0..9]>$/ )
  !! ( die "Must be 1..9 only"
         unless all((@values1, @values2).flat) ~~ /^<[1..9]>$/ );

my $l1 = LinkedList1.add-elems(@values1>>.Int);             # [4]
my $l2 = LinkedList1.add-elems(@values2>>.Int);             # [4]

say "L1:    ", $l1.nice if $verbose;
say "L2:    ", $l2.nice if $verbose;

my @values3 = ( $l1.nice("") + $l2.nice("") ).comb>>.Int;  # [5]

my $l3 = LinkedList1.add-elems(@values3);                  # [6]

say "L1+L2: ", $l3.nice if $verbose;

say $l3.nice;                                              # [7]

[1] Specify the two lists as space separated strings.

[2] Get the inidvidual values from both strings.

[3] Validate the individual values. Note the flat used on the combination of both lists, as the any junction expects a (as in one list without recursion).

[4] Generate the two linked lists.

[5] Get the values for the combined list. We get the values from the first list, as a string without spaces (nice(""). That string is coerced to an integer value by the addition. The right hand side is the same for the second list. We have now generated the value for the combined list by integer addition. Then we split it into separate digits, coerced into integers as the constructor (in [6]) expects that.

[6] Generate the new list.

[7] Print it, nicely.

Running it:

$ ./add-linked-list
4 => 4 => 4

$ ./add-linked-list "1 2 3 4 5" "6 5 5"
1 => 3 => 0 => 0 => 0

Looking good.

With verbose mode:

$ ./add-linked-list -v 
L1:    1 => 2 => 3
L2:    3 => 2 => 1
L1+L2: 4 => 4 => 4
4 => 4 => 4

$ ./add-linked-list -v "1 2 3 4 5" "6 5 5"
L1:    1 => 2 => 3 => 4 => 5
L2:    6 => 5 => 5
L1+L2: 1 => 3 => 0 => 0 => 0
1 => 3 => 0 => 0 => 0

The challenge asked for a method adding two lists. We have not done so, so let us add one (method, that is):

File: lib/LinkedList2.rakumod (changes only)
  method sum (LinkedList2 $l1, LinkedList $l1)
  {
    die "Do not call on an object" if self ~~ LinkedList2:D;   # [1]
  
    return LinkedList2.add-elems(( $l1.nice("") + $l2.nice("") ).comb>>.Int);
  }                                                            # [2]

[1] Ensure that we have not invoked it on an object, as both lists are supposed to be passed as arguments.

[2] We did this in the program, now it is hidden inside a method.

The program:

File: add-linked-list2
#! /usr/bin/env raku

use lib "lib";
use LinkedList2;

unit sub MAIN (Str $string1 = '1 2 3',
               Str $string2 = '3 2 1',
                  :z(:$zero),
                  :v(:$verbose));

my @values1 = $string1.words;
my @values2 = $string2.words;

$zero
  ?? (die "Must be 0..9 only" unless all((@values1, @values2).flat) ~~ /^<[0..9]>$/)
  !! (die "Must be 1..9 only" unless all((@values1, @values2).flat) ~~ /^<[1..9]>$/);

my $l1 = LinkedList2.add-elems(@values1>>.Int);
my $l2 = LinkedList2.add-elems(@values2>>.Int);

say "L1:    ", $l1.nice if $verbose;
say "L2:    ", $l2.nice if $verbose;

my $l3 = LinkedList2.sum($l1, $l2);     # [1]

say "L1+L2: ", $l3.nice if $verbose;

say $l3.nice;

[1] Magic...

Running it gives the same result as before:

$ ./add-linked-list2 
4 => 4 => 4

$ ./add-linked-list2 "1 2 3 4 5" "6 5 5"
1 => 3 => 0 => 0 => 0

Note that the idea of keeping the node value restriction outside of the class does not make any sense after adding (pun intended) the «add» method. So we should really move the restriction and check inside the class.

File: lib/LinkedList3.rakumod
class LinkedList3
{
  has Int         $.value;
  has LinkedList3 $.next is rw;

  method from-string ($string, :z(:$zero))                    # [2]
  {
    die "Do not call on an object" if self ~~ LinkedList3:D;  # [2a]
    
    my @values = $string.comb.grep({ /\S/ });                 # [3]

    _validate(@values, :$zero);                               # [1a]

    return LinkedList3.add-elems(@values>>.Int, :$zero);
  }

  method add-elems (@values, :r(:$replace-next), :z(:$zero))
  {
    _validate(@values, :$zero);                               # [1b]

    my $list  = self;
    my $first = @values[0];
    my $obj   = LinkedList3.new(value => $first);

    if $list ~~ LinkedList3:D
    {
      unless $replace-next { $list = $list.next while $list.next; }
      $list.next = $obj;
    }
    else
    {
      $list = $obj;
    }

    $obj.add-elems(@values[1 .. *-1], :$zero) if @values.elems > 1;

    return $obj;
  }

  method recursive-value
  {
    return flat(self.value, self.next.recursive-value) if self.next;
    return self.value;
  }

  method nice (Str $sep = ' => ')
  {
    return self.recursive-value.join($sep);
  }

  method sum (LinkedList3 $l1, LinkedList3 $l2)
  {
    die "Do not call on an object" if self ~~ LinkedList3:D;
    
    return LinkedList3.from-string( ( $l1.nice("") + $l2.nice("") ), :zero );
  }                                        # [4]

  sub _validate(@values, :z(:$zero))       # [1]
  {
    $zero
      ?? ( die "Must be 0..9 only" unless all(@values) ~~ /^<[0..9]>$/ )
      !! ( die "Must be 1..9 only" unless all(@values) ~~ /^<[1..9]>$/ );

    return True;
  }
}

[1] The validation code is used by two methods ([1a] and [1b]), so here it is as an (internal) procedure. Note the support for zero, with a flag.

[2] A new constructor method that takes a string, instead of the list used by «add-elems». It does not support adding to an existing list, so can only be called on the class itself (not an object) [2a].

[3] This splits the string into separate characters (with comb) and gets rid of spaces (with grep({ /\S/ }), that matches non-space characters and lets them through). This makes it possible to call the procedure with a space separated string, or one without spaces; e.g. «1 2 3» or «123».

[4] Explicitly allow zeroes in the combined value, even if not allowed in the two user supplied values - as e.g. «1 5 5« and «1 4 5» give «3 0 0».

Note that the validation of the remaning list of digits is done for each recursive iteration. That is somewhat excessive, but it does not do any harm. As long as we remeber to pass on :zero in [4].

The program is rather short:

File: add-linked-list3
#! /usr/bin/env raku

use lib "lib";
use LinkedList3;

unit sub MAIN (Str $string1 = '1 2 3',
               Str $string2 = '3 2 1',
                   :z(:$zero),
                   :v(:$verbose));

my $l1 = LinkedList3.from-string($string1, :$zero);
my $l2 = LinkedList3.from-string($string2, :$zero);

my $l3 = LinkedList3.sum($l1, $l2);

if $verbose
{
  say "L1:    ", $l1.nice;
  say "L2:    ", $l2.nice;
  say "L1+L2: ", $l3.nice;
}

say $l3.nice;

It would be even shorter if we remove the verbose handling code. Feel free to do so…

Running it:

$ ./add-linked-list3
4 => 4 => 4

$ ./add-linked-list3 "1 2 3" "3 2 1"
4 => 4 => 4

$ ./add-linked-list3 "123" "321"
4 => 4 => 4

With verbose mode:

$ ./add-linked-list3 -v
L1:    1 => 2 => 3
L2:    3 => 2 => 1
L1+L2: 4 => 4 => 4
4 => 4 => 4

$ ./add-linked-list3 -v "1 2 3" "3 2 1"
L1:    1 => 2 => 3
L2:    3 => 2 => 1
L1+L2: 4 => 4 => 4
4 => 4 => 4

$ ./add-linked-list3 -v "123" "321"
L1:    1 => 2 => 3
L2:    3 => 2 => 1
L1+L2: 4 => 4 => 4
4 => 4 => 4

And that's it.