Zero Step
with Raku

by Arne Sommer

Zero Step with Raku

[323] Published 1. January 2025.

This is my response to The Weekly Challenge #302.

Challenge #302.1: Ones and Zeroes

You are given an array of binary strings, @str, and two integers, $x and $y.

Write a script to return the size of the largest subset of @str such that there are at most $x 0’s and $y 1’s in the subset.

A set m is a subset of n if all elements of m are also elements of n.

Example 1:
Input: @str = ("10", "0001", "111001", "1", "0")
       $x = 5
       $y = 3
Output: 4

The largest subset with at most five 0's and three 1's:
("10", "0001", "1", "0")
Example 2:
Input: @str = ("10", "1", "0")
       $x = 1
       $y = 1
Output: 2

The largest subset with at most one 0's and one 1's:
("1", "0")
File: ones-and-zeroes
#! /usr/bin/env raku

subset PosInt of Int where * > 0;

unit sub MAIN (*@str where @str.elems > 1 && all(@str) ~~ /^<[01]>+$/, # [1]
               :a(:$all)            = 0,                               # [2]
               PosInt :o(:$ones)    = 0,                               # [3]
               PosInt :z(:$zeroes)  = 0,                               # [4]
               :v(:$verbose) = $all);                                  # [2a]

my $output = 0;                                                        # [5]

for @str.combinations(1 .. *).reverse -> @combo                        # [6]
{
  my %freq = @combo.join.comb.Bag;                                     # [7]
  %freq<0> //= 0;                                                      # [7a]
  %freq<1> //= 0;                                                      # [7b]

  if %freq<0> <= $zeroes && %freq<1> <= $ones                          # [8]
  {
    say ": + { @combo.join(",") } -> 0:%freq<0> 1:%freq<1> [{ @combo.elems }]"
      if $verbose;
					
    $output = @combo.elems unless $output;                             # [5a]
    last unless $all;                                                  # [2b]
  }
  elsif $verbose
  {
    say ": - { @combo.join(",") } -> 0:%freq<0> 1:%freq<1>";
  }
}

say $output;                                                           # [5b]

[1] Two or more strings consisting of one or more binary digits only.

[2] The program will stop after the first match (the longest), but use the «all» option if you want to see all of them (handled by [2b]). This option enables verbose as well [2a], as it would not make sense otherwise.

[3] I find o (ones) better than x, so have chosen to use that instead. The program will die if the named argument is not supplied, as the default value (zero) is ilegal. An explicit die as default value will also work.

[4] z (zeroes) instead of y.

[5] The result, collected here so that «all» mode can do its job. Update the value, if not already set [5a]. Print the value if set [5b]. Note that initialising it to zero works, as the lowest possible answer is 1.

[6] All possible combinations, excluding the one with no elements. Note that the argument to combinations is a Range, which cannot count dowm so we have to reverse the result so that we get the largest subset first.

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

[7] Get the count of ones and zeroes, courtesy of a Bag that we then assign to a plain hash. Ensure that the two hash values exist [7a,b], so that looking them up later on does not give warnings.

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

[8] Is the rule in the challenge satisfied? If so, save the length [5a] if not already done, and exit [2b] unless «all» mode is used.

Running it:

$ ./ones-and-zeroes -z=5 -o=3 10 0001 111001 1 0
4

$ ./ones-and-zeroes -z=1 -o=1 10 1 0
2

Looking good.

With verbose mode:

$ ./ones-and-zeroes -v -z=5 -o=3 10 0001 111001 1 0
: - 10,0001,111001,1,0 -> 0:7 1:7
: - 0001,111001,1,0 -> 0:6 1:6
: - 10,111001,1,0 -> 0:4 1:6
: + 10,0001,1,0 -> 0:5 1:3 [4]
4

$ ./ones-and-zeroes -v -z=1 -o=1 10 1 0
: - 10,1,0 -> 0:2 1:2
: + 1,0 -> 0:1 1:1 [2]
2

With «all» mode:

$ ./ones-and-zeroes -a -z=5 -o=3 10 0001 111001 1 0
: - 10,0001,111001,1,0 -> 0:7 1:7
: - 0001,111001,1,0 -> 0:6 1:6
: - 10,111001,1,0 -> 0:4 1:6
: + 10,0001,1,0 -> 0:5 1:3 [4]
: - 10,0001,111001,0 -> 0:7 1:6
: - 10,0001,111001,1 -> 0:6 1:7
: - 111001,1,0 -> 0:3 1:5
: + 0001,1,0 -> 0:4 1:2 [3]
: - 0001,111001,0 -> 0:6 1:5
: - 0001,111001,1 -> 0:5 1:6
: + 10,1,0 -> 0:2 1:2 [3]
: - 10,111001,0 -> 0:4 1:5
: - 10,111001,1 -> 0:3 1:6
: + 10,0001,0 -> 0:5 1:2 [3]
: + 10,0001,1 -> 0:4 1:3 [3]
: - 10,0001,111001 -> 0:6 1:6
: + 1,0 -> 0:1 1:1 [2]
: - 111001,0 -> 0:3 1:4
: - 111001,1 -> 0:2 1:5
: + 0001,0 -> 0:4 1:1 [2]
: + 0001,1 -> 0:3 1:2 [2]
: - 0001,111001 -> 0:5 1:5
: + 10,0 -> 0:2 1:1 [2]
: + 10,1 -> 0:1 1:2 [2]
: - 10,111001 -> 0:3 1:5
: + 10,0001 -> 0:4 1:2 [2]
: + 0 -> 0:1 1:0 [1]
: + 1 -> 0:0 1:1 [1]
: - 111001 -> 0:2 1:4
: + 0001 -> 0:3 1:1 [1]
: + 10 -> 0:1 1:1 [1]
4

$ ./ones-and-zeroes -a -z=1 -o=1 10 1 0
: - 10,1,0 -> 0:2 1:2
: + 1,0 -> 0:1 1:1 [2]
: - 10,0 -> 0:2 1:1
: - 10,1 -> 0:1 1:2
: + 0 -> 0:1 1:0 [1]
: + 1 -> 0:0 1:1 [1]
: + 10 -> 0:1 1:1 [1]
2

No subset is also a result:

$ ./ones-and-zeroes -v -a -z=1 -o=1 11 110 001 0001
: - 11,110,001,0001 -> 0:6 1:6
: - 110,001,0001 -> 0:6 1:4
: - 11,001,0001 -> 0:5 1:4
: - 11,110,0001 -> 0:4 1:5
: - 11,110,001 -> 0:3 1:5
: - 001,0001 -> 0:5 1:2
: - 110,0001 -> 0:4 1:3
: - 110,001 -> 0:3 1:3
: - 11,0001 -> 0:3 1:3
: - 11,001 -> 0:2 1:3
: - 11,110 -> 0:1 1:4
: - 0001 -> 0:3 1:1
: - 001 -> 0:2 1:1
: - 110 -> 0:1 1:2
: - 11 -> 0:0 1:2
0

Challenge #302.2: Step by Step

You are given an array of integers, @ints.

Write a script to find the minimum positive start value such that step by step sum is never less than one.

Example 1:
Input: @ints = (-3, 2, -3, 4, 2)
Output: 5

For start value 5.
5 + (-3) = 2
2 + (+2) = 4
4 + (-3) = 1
1 + (+4) = 5
5 + (+2) = 7
Example 2:
Input: @ints = (1, 2)
Output: 1
Example 3:
Input: @ints = (1, -2, -3)
Output: 5
File: step-by-step
#! /usr/bin/env raku

unit sub MAIN (*@ints where @ints.elems > 1 && all(@ints) ~~ Int,  # [1]
               :v(:$verbose));

NXT: for 1 .. * -> $start                                          # [2]
{
  say ": Start value $start" if $verbose;
  
  my $sum = $start;                                                # [3]

  for @ints -> $int                                                # [4]
  {
    say ": $sum + ({ $int < 0 ?? $int !! "+$int" }) = { $sum + $int }"
      if $verbose;
										
    $sum += $int;                                                  # [5]
    next NXT if $sum < 1;                                          # [6]
  }

  say $start;                                                      # [7]
  last;                                                            # [8]
}

[1] Ensure at least 2 integers.

[2] Iterate over the non-negative integers, starting at the lowest. This is the candidate for the «minimum positive start value» we are looking for.

[3] The current sum, starting with (at) the start value.

[4] Iterate over the input values.

[5] • Add the current value.

[6] • Skip this iteration of the outer loop [2] if the condition is not met.

[7] Failure to fail (in [6]) means success. Say so.

[8] We are done; exit the loop (and the program).

Running it:

$ ./step-by-step -- -3 2 -3 4 2
5

$ ./step-by-step 1 2
1

$ ./step-by-step 1 -2 -3
5

Looking good.

The -- (double dash) argument is there to tell the system to stop parsing the arguments as command line options. We do not want it to treat -3 as a command line option.

With verbose mode:

$ ./step-by-step -v -- -3 2 -3 4 2
: Start value 1
: 1 + (-3) = -2
: Start value 2
: 2 + (-3) = -1
: Start value 3
: 3 + (-3) = 0
: Start value 4
: 4 + (-3) = 1
: 1 + (+2) = 3
: 3 + (-3) = 0
: Start value 5
: 5 + (-3) = 2
: 2 + (+2) = 4
: 4 + (-3) = 1
: 1 + (+4) = 5
: 5 + (+2) = 7
5

$ ./step-by-step -v 1 2
: Start value 1
: 1 + (+1) = 2
: 2 + (+2) = 4
1

$ ./step-by-step -v 1 -2 -3
: Start value 1
: 1 + (+1) = 2
: 2 + (-2) = 0
: Start value 2
: 2 + (+1) = 3
: 3 + (-2) = 1
: 1 + (-3) = -2
: Start value 3
: 3 + (+1) = 4
: 4 + (-2) = 2
: 2 + (-3) = -1
: Start value 4
: 4 + (+1) = 5
: 5 + (-2) = 3
: 3 + (-3) = 0
: Start value 5
: 5 + (+1) = 6
: 6 + (-2) = 4
: 4 + (-3) = 1
5

And that's it.