by Arne Sommer

# Sorted Expenditure with Raku

 Published 31. May 2023.

This is my response to The Weekly Challenge #219.

## Challenge #219.1: Sorted Squares

You are given a list of numbers.

Write a script to square each number in the list and return the sorted list, increasing order.

Example 1: ```Input: @list = (-2, -1, 0, 3, 4) Output: (0, 1, 4, 9, 16) ``` Example 2: ```Input: @list = (5, -4, -1, 3, 6) Output: (1, 9, 16, 25, 36) ```

File: sorted-squares ```#! /usr/bin/env raku unit sub MAIN (*@list where @list.elems >= 1 && all(@list) ~~ Int); #  say '(' ~ @list.map( * ** 2 ).sort.join(", ") ~ ')'; #  ```

 Ensure at least one value. I have chosen to restrict them to integers, as that is used in the examples. Replace `Int` with `Numeric` if you want to adhere to the challenge text instead.

 Use `map` to square the values (`** 2`), then `sort` to sort them in ascending order, and `join` to print the commas between the values. The first `*` is a placeholder for the current value. (You cannot use the more impressive looking `( * * * )`, as the last `*` will be taken as a second placeholder. We have only one input value for each iteration of `map`.)

See docs.raku.org/routine/map for more information about `map`.

Running it:

```\$ ./sorted-squares -- -2 -1 0 3 4 (0, 1, 4, 9, 16) \$ ./sorted-squares 5 -4 -1 3 6 (1, 9, 16, 25, 36) ```

Looking good.

As a oneliner, without the input guard rails:

```say '(' ~ @*ARGS.map( * ** 2 ).sort.join(", ") ~ ')'; ```

## Challenge #219.2: Travel Expenditure

You are given two list, @costs and @days.

The list @costs contains the cost of three different types of travel cards you can buy.

For example @costs = (5, 30, 90)

```Index 0 element represent the cost of 1 day travel card. Index 1 element represent the cost of 7 days travel card. Index 2 element represent the cost of 30 days travel card. ``` The list @days contains the day number you want to travel in the year.

For example: @days = (1, 3, 4, 5, 6)

The above example means you want to travel on day 1, day 3, day 4, day 5 and day 6 of the year.

Write a script to find the minimum travel cost.

Example 1: ```Input: @costs = (2, 7, 25) @days = (1, 5, 6, 7, 9, 15) Output: 11 On day 1, we buy a one day pass for 2 which would cover the day 1. On day 5, we buy seven days pass for 7 which would cover days 5 - 9. On day 15, we buy a one day pass for 2 which would cover the day 15. So the total cost is 2 + 7 + 2 => 11. ``` Example 2: ```Input: @costs = (2, 7, 25) @days = (1, 2, 3, 5, 7, 10, 11, 12, 14, 20, 30, 31) Output: 20 On day 1, we buy a seven days pass for 7 which would cover days 1 - 7. On day 10, we buy a seven days pass for 7 which would cover days 10 - 14. On day 20, we buy a one day pass for 2 which would cover day 20. On day 30, we buy a one day pass for 2 which would cover day 30. On day 31, we buy a one day pass for 2 which would cover day 31. So the total cost is 7 + 7 + 2 + 2 + 2 => 20. ```

I am going to use recursion, because I am going to use recursion. And that is because I am... (You get the point, surely.)

File: travel-expenditure (partial) ```#! /usr/bin/env raku unit sub MAIN (\$costs, \$days, :v(:\$verbose)); #  my @costs = \$costs.words>>.Numeric; #  die "Specify three costs" unless @costs.elems == 3; #  die "Costs must be numeric" unless all(@costs) ~~ Numeric; #  my %costs = ( 1 => @costs, 7 => @costs, 30 => @costs ); #  my @days = \$days.words>>.UInt; #  die "The days must be positive integers (1..366)" #  unless all(@days) ~~ UInt && 1 <= all(@days) <= 366; my @res = gather #  { recurse(log => (), total-cost => 0, todo => @days.sort); #  } my \$lowest = Inf; #  for @res -> \$row #  { print ": Cost: \$row{'cost'}" if \$verbose; if \$row{'cost'} < \$lowest #  { \$lowest = \$row{'cost'}; # [12a] say " - new lowest" if \$verbose; } elsif \$verbose { say ""; } if \$verbose { for @(\$row{'log'}) -> \$log { say ": - \$log"; } say ""; } } say \$lowest; #  ```

 The costs and days as separate space separated strings.

 Split the costs into seperate values (with `words`) and coerce those values to numbers (from the default string we get by `words`). This will cause a run time error if the coersion fails, but that does not matter. Yet./p>

#### Deferred Errors

Coercing the input to `Numeric` (or `UInt` in ) will fail when passed an un-coercable value. But the failure will only happen when we use the value, as we do in the error checking ( and ).

Note that REPL mode tricks you up here, as it prints the last value, thus usually triggering an exception up front - what you would expect if you are not familiar with Raku Deferriment.

See e.g. my Dynamic Zero with Raku article for more details.

 The cost array must have exactly three elements.

 All the values must be numeric. This will trigger a deferred error from the coersion in  if they are not.

 Set up the costs as a hash, with the number of days as keys.

 Get the days, coerced to integers.

 This will also potentially trigger a deferred error (from ). Also note the chained comparison to ensure that the day numbers are reasonable (1..366; in case of a leap year).

 Using `gather`/`take` to set up the sequence (of possible combinations) is ideal here, as we return values (with `take`) are returned (so to speak) inside a recursive procedure - one at a time. Be aware that this is a brute force approach, which potenially will do a lot of computations (as shown when using verbose mode on the second example).

See my Raku Gather, I Take article or docs.raku.org/syntax/gather take for more information about `gather`/`take`.

 Off we go, recursively. The procedure uses named arguments; «log» is an array of verbose explanation, «total-cost» is the total cost so far, and «todo» is a (sorted) list of days yet to parse.

The result of the block in  and  is an array of values, returned one at a time by `take` somewhere in the recursive procedure. We will get back to that later.

 We are looking for the (or a) solution with the lowest total cost. So we start off with the highest possible value.

 Iterate over the results.

 Each result is a hash. Look up the cost, and save that as the current lowest cost if it is lower than the previously lowest value.

 Print the total cost.

File: travel-expenditure (the rest) ```sub recurse (:@log, :\$total-cost, :@todo is copy) #  { my \$today = @todo.shift; #  for 1,7,30 -> \$days #  { my \$paid = \$total-cost + %costs{\$days}; #  my \$paid-to = \$today + \$days; #  my @covered = (\$today,); #  my @new-todo = @todo.clone; #  my @new-log = @log.clone; #  while @todo.elems && @todo < \$paid-to #  { @covered.push: @todo.shift; } @new-log.push: "At day \$today pay for \$days day(s) ({ @covered.join(",") })"; if @todo.elems #  { recurse(log => @new-log, total-cost => \$paid, todo => @new-todo) } else #  { take { cost => \$paid, log => @new-log }; } } } ```

 The signature of the procedure. Note the `:` prefixes, indicating named arguments.

 Iterate over the different ticket period types.

 The cost so far, including this ticket.

 The last day of the current ticket period.

 The days (given by the user) that are covered by the current ticket will end up here (for verbose output usage).

 Get a copy of this one, as we are removing values from it (in ) that should only apply inside the scope of the current iteration of the for loop.

 Ditto, but we are adding to this one.

 As long as the first unparsed day is covered by the current ticket, move the day from the todo list to the list of covered dates.

 Have we covered all the dates? If not, go on recursively (until we do so).

 If we have, return the cost and log lines (with `take`).

Running it:

```\$ ./travel-expenditure "5 30 90" "1 3 4 5 6" 25 \$ ./travel-expenditure "2 7 25" "1 5 6 7 9 15" 12 \$ ./travel-expenditure "2 7 25" "1 2 3 5 7 10 11 12 14 20 30 31" 20 ```

Looking good. (The first one is the unnumbered example in the challenge text, perhaps a candidate for the «example 0» moniker.)

With verbose mode:

```\$ ./travel-expenditure -v "5 30 90" "1 3 4 5 6" : Cost: 25 - new lowest : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 1 day(s) (4) : - At day 5 pay for 1 day(s) (5) : - At day 6 pay for 1 day(s) (6) : Cost: 50 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 1 day(s) (4) : - At day 5 pay for 1 day(s) (5) : - At day 6 pay for 7 day(s) (6) : Cost: 110 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 1 day(s) (4) : - At day 5 pay for 1 day(s) (5) : - At day 6 pay for 30 day(s) (6) : Cost: 45 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 1 day(s) (4) : - At day 5 pay for 7 day(s) (5,6) : Cost: 105 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 1 day(s) (4) : - At day 5 pay for 30 day(s) (5,6) : Cost: 40 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 7 day(s) (4,5,6) : Cost: 100 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 1 day(s) (3) : - At day 4 pay for 30 day(s) (4,5,6) : Cost: 35 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 7 day(s) (3,4,5,6) : Cost: 95 : - At day 1 pay for 1 day(s) (1) : - At day 3 pay for 30 day(s) (3,4,5,6) : Cost: 30 : - At day 1 pay for 7 day(s) (1,3,4,5,6) : Cost: 90 : - At day 1 pay for 30 day(s) (1,3,4,5,6) 25 ```

The first example produces 132 lines of verbose output, so I have removed most of them:

```\$ ./travel-expenditure -v "2 7 25" "1 5 6 7 9 15" ... : Cost: 11 - new lowest : - At day 1 pay for 1 day(s) (1) : - At day 5 pay for 7 day(s) (5,6,7,9) : - At day 15 pay for 1 day(s) (15) ... 11 ```

The second example produces a staggering 2517 lines of verbose output, so here is the relevant part:

```\$ ./travel-expenditure -v "2 7 25" "1 2 3 5 7 10 11 12 14 20 30 31" ... : Cost: 20 - new lowest : - At day 1 pay for 7 day(s) (1,2,3,5,7) : - At day 10 pay for 7 day(s) (10,11,12,14) : - At day 20 pay for 1 day(s) (20) : - At day 30 pay for 1 day(s) (30) : - At day 31 pay for 1 day(s) (31) ... 20 ```

And that's it.