Median Box
with Raku

by Arne Sommer

Median Box with Raku

[416] Published 20. September 2026.

This is my response to The Weekly Challenge #391.

#391.1 Array Median You are given two sorted arrays.

Write a script to merge the two given sorted arrays and return the median of the merged array.

Example 1:
Input: @arr1 = (2), @arr2 = (4)
Output: 3.0

Merged array: (2,4)
Median: (2+4)/2 => 3
Example 2:
Input: @arr1 = (1,2,3), @arr2 = (7,8,9,10)
Output: 7.0

Merged array: (1,2,3,7,8,9,10)
Length of merged array is 7, the 4th element is 7.
Example 3:
Input: @arr1 = (), @arr2 = (10,20,30,40)
Output: 25.0

Merged array: (10,20,30,40)
Median: (20+30)/2 => 25
Example 4:
Input: @arr1 = (100), @arr2 = (1,2,3,4,5,6,7)
Output: 4.5

Merged array: (1,2,3,4,5,6,7,100)
Median: (4+5)/2 => 4.5
Example 5:
Input: @arr1 = (1,2,2), @arr2 = (2,2,3)
Output: 2.0

Merged array: (1,2,2,2,2,3)
Median: (2+2)/2 => 2
File: array-median
#! /usr/bin/env raku

unit sub MAIN ($arr1, $arr2, :v(:$verbose));

my @arr1   = $arr1.words>>.Numeric;
my @arr2   = $arr2.words>>.Numeric;
my @sorted = (@arr1, @arr2).flat.sort;
my $index  = (@sorted.elems - 1) div 2;

if $verbose
{
  say ": Merged: @sorted[]";
  say ": Median index: $index";
}

say @sorted.elems % 2
  ?? @sorted[$index]
  !! (@sorted[$index] + @sorted[$index +1]) / 2;

[3] The two arrays as two space separated string of values.

[5] Split the first array string into an array, and make sure that the values are numeric. (This will abort the program on non-numeric input, and has the added benefit of making verbose mode look nicer.)

[6] The second one.

[7] Merging two arrays like this gives a new array with two elemens - the original arrays, so we slap on a .flat to get a single array. Then we sort it.

[8] Get the index of the median (but see [16]), using integer division with div.

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

[16] Do we have an odd number of elements?

[17] Yes: print the value with the given index.

[18] No: get the average of the value with the index and the next one.

Running it:

$ ./array-median "2" "4"
3

$ ./array-median "1 2 3" "7 8 9 10"
7

$ ./array-median "" "10 20 30 40"
25

$ ./array-median "100" "1 2 3 4 5 6 7"
4.5

$ ./array-median "1 2 2" "2 2 3"
2

Looking good.

With verbose mode:

$ ./array-median -v "2" "4"
: Merged: 2 4
: Median index: 0
3

$ ./array-median -v "1 2 3" "7 8 9 10"
: Merged: 1 2 3 7 8 9 10
: Median index: 3
7

$ ./array-median -v "" "10 20 30 40"
: Merged: 10 20 30 40
: Median index: 1
25

$ ./array-median -v "100" "1 2 3 4 5 6 7"
: Merged: 1 2 3 4 5 6 7 100
: Median index: 3
4.5

$ ./array-median -v "1 2 2" "2 2 3"
: Merged: 1 2 2 2 2 3
: Median index: 2
2

The two input arrays are sorted, according to the challenge text, so the intention could have been for us to merge them manually. So let us do just that:

File: array-median-manual
#! /usr/bin/env raku

unit sub MAIN ($arr1, $arr2, :v(:$verbose));

my @arr1   = $arr1.words>>.Numeric;
my @arr2   = $arr2.words>>.Numeric;
my @sorted = merge-sort(@arr1, @arr2);
my $index  = (@sorted.elems - 1) div 2;
my $single = @sorted.elems % 2;

if $verbose
{
  say ": Merged: @sorted[]";
  say ": Median index: $index" ~ ( $single ?? "" !! "+" );
}

say $single
  ?? @sorted[$index]
  !! (@sorted[$index] + @sorted[$index +1]) / 2;

sub merge-sort (@arr1 is copy, @arr2 is copy)
{
  return gather
  {
    while @arr1.elems || @arr2.elems
    {
      if @arr1.elems && @arr2.elems
      {
        @arr1[0] < @arr2[0]
          ?? take @arr1.shift
          !! take @arr2.shift;
      }
      elsif @arr1.elems
      {
        take @arr1.shift;
      }
      else # @arr2.elems
      {
        take @arr2.shift;
      }
    }
  }
}

[7] Merge the two arrays, using a custom procedure.

[9] Do we have single median value, i.e. an odd number of elements?

[14] Note the added "+" when we have an even number of elements. The original program is not accurate here.

[23] Use gather to collect the sorted values for the return statement.

See docs.raku.org/routine/gather/take for more information about gather/take.

[25] As long as at least one of the arrays is not empty.

[27] values in both? Take the lowest one.

[33] Values in the left one only? Take from that one.

[37] You get the idea...

Running it, with verbose mode:

$ ./array-median-manual -v "2" "4"
: Merged: 2 4
: Median index: 0+
3

$ ./array-median-manual -v "1 2 3" "7 8 9 10"
: Merged: 1 2 3 7 8 9 10
: Median index: 3
7

$ ./array-median-manual -v "" "10 20 30 40"
: Merged: 10 20 30 40
: Median index: 1+
25

$ ./array-median-manual -v "100" "1 2 3 4 5 6 7"
: Merged: 1 2 3 4 5 6 7 100
: Median index: 3+
4.5

$ ./array-median-manual -v  "1 2 2" "2 2 3"
: Merged: 1 2 2 2 2 3
: Median index: 2+
2

Looking good.


#391.2 Arrange Box You are given an array of box dimensions.

Write a script to determine the maximum number of these boxes that can fit inside each other in a single stack. For a box to fit inside another, it must be smaller in both dimensions.

Example 1:
Input: @boxes = ([1, 3], [3, 5], [6, 8], [2, 4])
Output: 4

Sort by width ascending: ([1, 3], [2, 4], [3, 5], [6, 8])
Extract heights: [3, 4, 5, 8]
[1, 3] -> [2, 4] -> [3, 5] -> [6, 8]
Example 2:
Input: @boxes = ([4, 5], [4, 6], [6, 7], [2, 3], [4, 3])
Output: 3

Sort by width ascending: ([2, 3], [4, 6], [4, 5], [4, 3], [6, 7])
Extract heights: (3, 6, 5, 3, 7)
[2, 3] -> [4, 5] -> [6, 7]
Example 3:
Input: @boxes = ([5, 5], [5, 5], [5, 5])
Output: 1

Sort by width ascending: ([5, 5], [5, 5], [5, 5])
Extract heights: (5, 5, 5)
[5, 5]
Example 4:
Input: @boxes = ([2, 100], [3, 200], [4, 300], [5, 50], [5, 400])
Output: 4

Sort by width ascending: ([2, 100], [3, 200], [4, 300], [5, 400], [5, 50])
Extract heights: (100, 200, 300, 400, 50)
[2, 100] -> [3, 200] -> [4, 300] -> [5, 400]
Example 5:
Input: @boxes = ([10, 20], [15, 10], [20, 30], [12, 18], [16, 25])
Output: 3

Sort by width ascending: ([10, 20], [12, 18], [15, 10], [16, 25], [20, 30])
Extract heights: (20, 18, 10, 25, 30)
[15, 10] -> [16, 25] -> [20, 30]
File: arrange-box
#! /usr/bin/env raku

unit sub MAIN (*@boxes, :v(:$verbose));

my @dims = @boxes>>.words>>.Numeric;

die "At least one box is required" unless @dims.elems;

die "Each box must have exactly two dimensions"
  unless all(@dims>>.elems) == 2;

my @sorted = @dims.sort: { (.[0], -.[1]) };

if $verbose
{
  say ": Sorted: " ~ @sorted.map({ "[$_[0], $_[1]]" }).join(", ");
  say ": Heights: " ~ @sorted>>[1].join(", ")
}
  
my @tails;

for @sorted -> $box
{
  my $lo = @tails.first({ .[*-1][1] >= $box[1] }, :k)
    // @tails.elems;

  @tails[$lo] = $lo > 0
    ?? @tails[$lo - 1].Array.append($box) !! [$box];
}

my $chain = @tails[*-1];

say ": Chain: " ~ $chain.map({ "[$_[0], $_[1]]" }).join(" -> ")
  if $verbose;

say $chain.elems;

[3] Specify the boxes as e.g. "1 3" "2 4".

[5] Convert the box strings into arrays, and ensure that the values are numeric.

[12] Sort the boxes by ascending width, then by descending height if the widths are the same.

[20] All the chains will end up here.

[22] Iterate over the sorted boxes.

[24] We are looking for an existing chain (from the left) where our current box is a better fit than the last element in that chain. We get the index with the :k adverb on first. No match means that we can add the box to the last chain (in [25]), without replacing anything.

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

[27] Do we have an existing chain to add to? If so, get a copy of the chain to the left of it, add the new box, and assign to the current chain. If not, use the current box only.

[31] The longest chain is always at the end. In general, a chain at index m has the length m+1.

[39] Print the length of the chain.

Overwriting a tails entry can happen, but that means that we found a better choice for the next value in this chain.

Running it:

$ ./arrange-box "1 3" "3 5" "6 8" "2 4"
4

$ ./arrange-box "4 5" "4 6" "6 7" "2 3" "4 3"
3

$ ./arrange-box "5 5" "5 5" "5 5"
1

$ ./arrange-box "2 100" "3 200" "4 300" "5 50" "5 400"
4

$ ./arrange-box "10 20" "15 10" "20 30" "12 18" "16 25"
3

Looking good.

With verbose mode:

$ ./arrange-box -v "1 3" "3 5" "6 8" "2 4"
: Sorted: [1, 3], [2, 4], [3, 5], [6, 8]
: Heights: 3, 4, 5, 8
: Chain: [1, 3] -> [2, 4] -> [3, 5] -> [6, 8]
4

$ ./arrange-box -v "4 5" "4 6" "6 7" "2 3" "4 3"
: Sorted: [2, 3], [4, 6], [4, 5], [4, 3], [6, 7]
: Heights: 3, 6, 5, 3, 7
: Chain: [2, 3] -> [4, 5] -> [6, 7]
3

$ ./arrange-box -v "5 5" "5 5" "5 5"
: Sorted: [5, 5], [5, 5], [5, 5]
: Heights: 5, 5, 5
: Chain: [5, 5]
1

$ ./arrange-box -v "2 100" "3 200" "4 300" "5 50" "5 400"
: Sorted: [2, 100], [3, 200], [4, 300], [5, 400], [5, 50]
: Heights: 100, 200, 300, 400, 50
: Chain: [2, 100] -> [3, 200] -> [4, 300] -> [5, 400]
4

$ ./arrange-box -v "10 20" "15 10" "20 30" "12 18" "16 25"
: Sorted: [10, 20], [12, 18], [15, 10], [16, 25], [20, 30]
: Heights: 20, 18, 10, 25, 30
: Chain: [15, 10] -> [16, 25] -> [20, 30]
3

And that's it.