Middle Hand
with Raku

by Arne Sommer

Middle Hand with Raku

[312] Published 20. October 2024.

This is my response to The Weekly Challenge #291.

Challenge #291.1: Middle Index

You are given an array of integers, @ints.

Write a script to find the leftmost middle index (MI) i.e. the smallest amongst all the possible ones.

A middle index is an index where ints[0] + ints[1] + … + ints[MI-1] == ints[MI+1] + ints[MI+2] + … + ints[ints.length-1].
  • If MI == 0, the left side sum is considered to be 0. Similarly,
  • if MI == ints.length - 1, the right side sum is considered to be 0.
Return the leftmost MI that satisfies the condition, or -1 if there is no such index.

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

The sum of the numbers before index 3 is: 2 + 3 + -1 = 4
The sum of the numbers after index 3 is: 4 = 4
Example 2:
Input: @ints = (1, -1, 4)
Output: 2

The sum of the numbers before index 2 is: 1 + -1 = 0
The sum of the numbers after index 2 is: 0
Example 3:
Input: @ints = (2, 5)
Output: -1

There is no valid MI.

The MI index can be any one of the legal indices. So we iterate over the values. For each value, we simply take the sum of the array to the left of it, and compare that with the sum of the array to the right of it.

When the index is the very first one (0; the first bullet point) we have to special case the values to the left of it to zero. This also applies when it is the very last one (ints.length - 1; the second bullet point), where we have to special case the values to the right of it - also to zero. The alternative is an array index error, that will terminate the program.

But we can cheat (for a given value of cheat), by adding a zero to the front and to the end of the input list. Then we iterate from the the second value (or index, rather) to the last but one. The first match, if any, is the answer - as we were asked for the leftmost. The only thing left to do is to compensate for the extra initial value, when we print the index.

File: middle-index
#! /usr/bin/env raku

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

@ints.unshift: 0;                                                  # [2]
@ints.push:    0;                                                  # [2a]

for 1 .. @ints.elems - 2 -> $MI                                    # [3]
{
  my @left  = @ints[0 .. $MI -1];                                  # [4]
  my @right = @ints[$MI +1 .. *];                                  # [4a]

  my $left-sum  = @left.sum;                                       # [5]
  my $right-sum = @right.sum;                                      # [5a]

  say ": MI:{ $MI -1 }: ({ @left[1..*].join(",")}) sum $left-sum <=> \
    ({ @right[0..^*-1].join(",")}) sum $right-sum" if $verbose;    # [8]

  if $left-sum == $right-sum                                       # [6]
  {
    say $MI -1;                                                    # [6a]
    exit;                                                          # [6b]
  }
}

say -1;                                                            # [7]

[1] At least one element (integer).

[2] Add a leading (unshift) and a trailing (push) zero [2a].

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

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

[3] Iterate over the MI indices, excluding the leading and trailing zeroes we just added.

[4] Get the values to the left of MI, and to the right [4a].

[5] Get the sum of the two arrays from [4,4a], with sum.

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

[6] Are the sums equal? If so, print the MI value - adjusted for the leading zero we added in [2], and exit.

[7] We have failed two find two sums that are uqual. Report as -1.

[8] I do not normally comment the verbose mode code, but the @right[0..^*-1] construct deserves mention. The * is the same as Inf, and subtraction is not really a thing with infinite values. It will be truncated to the last index in the array, but that happens after the -1 has failed to make a dent. The solution is this strange syntax, where the initial 0.. does not make sense, as the upto-but-not-including operator ^ starts at zero anyway. But here, it ensures that * is evaluated to the length, before the -1 part is applied.

Running it:

$ ./middle-index 2 3 -1 8 4
3

$ ./middle-index 1 -1 4
2

$ ./middle-index 2 5
-1

Looking good.

With verbose mode:

$ ./middle-index -v 2 3 -1 8 4
: MI:0: () sum 0 <=> (3,-1,8,4) sum 14
: MI:1: (2) sum 2 <=> (-1,8,4) sum 11
: MI:2: (2,3) sum 5 <=> (8,4) sum 12
: MI:3: (2,3,-1) sum 4 <=> (4) sum 4
3

$ ./middle-index -v 1 -1 4
: MI:0: () sum 0 <=> (-1,4) sum 3
: MI:1: (1) sum 1 <=> (4) sum 4
: MI:2: (1,-1) sum 0 <=> () sum 0
2

$ ./middle-index -v 2 5
: MI:0: () sum 0 <=> (5) sum 5
: MI:1: (2) sum 2 <=> () sum 0
-1

An array with a single element works quite well. The (empty) lists on both sides defaults to zero, are equal, and the MI index of 0 is returned:

$ ./middle-index -v 9999
: MI:0: () sum 0 <=> () sum 0
0

Challenge #291.2: Poker Hand Rankings

A draw poker hand consists of 5 cards, drawn from a pack of 52: no jokers, no wild cards. An ace can rank either high or low.

Write a script to determine the following three things:
  1. How many different 5-card hands can be dealt?
  2. How many different hands of each of the 10 ranks can be dealt? See here for descriptions of the 10 ranks of Poker hands: https://en.wikipedia.org/wiki/List_of_poker_hands#Hand-ranking_categories
  3. Check the ten numbers you get in step 2 by adding them together and showing that they're equal to the number you get in step 1.

I have not found an obvious way of calculating the 10th rank, but cheating (step 1 - the sum of rank 1..9) works. Kind of. This will of course always show that the sum of rank 1..10 equals step 1, as any rank value is essentially compared with itself. So errors, one or more, will not be detected.

So I have used https://en.wikipedia.org/wiki/Poker_probability to verify that I got the correct value in each rank.

A poker hand (any hand, really) does not have a sorting order, so we have to get rid of order in the computations. This is easy; the number of permutations is the faculty of the number of items; i.e. 5*4*3*2*1 for an array with 5 elements. This is generally shown as "order is irrelevant" in the comments.

File: poker-hand-rankings
#! /usr/bin/env raku

my $five_card_hands = five_card_hands;
my $padding = $five_card_hands.chars;

say "Five Card Hands: $five_card_hands\n";

my %ten_ranks     = ten_ranks;
my $ten_ranks_sum = %ten_ranks.values.sum;

say "Five of a Kind:  { %ten_ranks<five-of-a-kind>.fmt('%' ~ $padding ~ "d") }";
say "Straight Flush:  { %ten_ranks<straight-flush>.fmt('%' ~ $padding ~ "d") }";
say "Four of a Kind:  { %ten_ranks<four-of-a-kind>.fmt('%' ~ $padding ~ "d") }";
say "Full House:      { %ten_ranks<full-house>.fmt('%' ~ $padding ~ "d") }";
say "Flush:           { %ten_ranks<flush>.fmt('%' ~ $padding ~ "d") }";
say "Straight:        { %ten_ranks<straight>.fmt('%' ~ $padding ~ "d") }";
say "Three of a Kind: { %ten_ranks<three-of-a-kind>.fmt('%' ~ $padding ~ "d") }";
say "Two Pair:        { %ten_ranks<two-pair>.fmt('%' ~ $padding ~ "d") }";
say "One Pair:        { %ten_ranks<one-pair>.fmt('%' ~ $padding ~ "d") }";
say "High Card:       { %ten_ranks<high-card>.fmt('%' ~ $padding ~ "d") }";
say "Sum:             { $ten_ranks_sum.fmt('%' ~ $padding ~ "d") }";

say  $ten_ranks_sum == $five_card_hands
  ?? "\nThe number of Five Card Hands and the Sum Matches"
  !! "\nThe number of Five Card Hands and the Sum does not Match";

sub five_card_hands
{
  return
    52 * 51 * 50 * 49 * 48   # The number of cards to choose from
  / (5 *  4 *  3 *  2 *  1); # The order is irrelevant
}

sub ten_ranks
{
  my %res;

  %res<five-of-a-kind> = 0; # As we do not allow jokers and wildcards

  %res<straight-flush> =
    10 * # 10 different intervals (see below)
     4;  #  4 suits to choose from

  # Intervals: 1-5, 2-6, 3-7, 4-8, 5-9, 6-10, 7-11, 8-12, 9-13, 10-1
 
  %res<four-of-a-kind> =
    13 *    # 13 kinds to choose from, pick one (all four cards)
    12 * 4; # 12 kinds left, choose one of a any suit.

  %res<full-house> =
    13 * 4 *   # Choose three cards of the same kind (=exclude one of 4 cards)
    12 * 4 * 3 # Choose two cards of the same kind, different than above
       / 2;    # The order of 4,5 is irrelevant
		   
  %res<flush> =
    13 * 12 * 11 * 10 * 9 *   # 5 cards of the same suit
     4                        # 4 suits to choose from
       / (5 * 4 * 3 * 2 * 1)  # Order is irrelevant
     - %res<straight-flush>;  # Remove Straight Flushes

  %res<straight> =
     4 * 4 * 4 * 4 * 4 *      # Four choices for each value
    10                        # 10 ranges (1-5 .. 9-13, 10-1)
      - %res<straight-flush>; # Get rid of Straight Flushes

  %res<three-of-a-kind> =
    13 * 4 *    # One of thirteen, 4 possibilites for * 3
    12 * 4 *    # Card 4; not the same value
    11 * 4      # Card 5; not the same valus
       / 2;     # The order of 4,5 is irrelevant
  
  %res<two-pair> =
    13 * 4 * 3  # Card 1 and 2
       / 2 *    # The order of 1,2 is irrelevant
    12 * 4 * 3  # Card 3 and 4
       / 2      # The order of 3,4 is irrelevant
       / 2 *    # The order of the two pairs is irrelevant
    11 * 4;     # Card 5

  %res<one-pair> =
    13 * 4 * 3        # Card 1 and 2.
       / 2 *          # The order of 1,2 is irrelevant
    12 * 4 *          # Card 3, diffferent value
    11 * 4 *          # Card 4, a second unused value
    10 * 4            # Card 5, a third unused value
       / (1 * 2 * 3); # The order of 3,4,5 is irrelevant
  
  %res<high-card> = (52 * 51 * 50 * 49 * 48) / (5 * 4 * 3 * 2 * 1)
                  - %res.values.sum;
 
  return %res;
}

I think the code is suitably commented inline. Except for the use of fmt, the method version of sprintf, used here to nicely tabulate the values from the right.

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

Running it:

$ ./poker-hand-rankings 
Five Card Hands: 2598960

Five of a Kind:        0
Straight Flush:       40
Four of a Kind:      624
Full House:         3744
Flush:              5108
Straight:          10200
Three of a Kind:   54912
Two Pair:         123552
One Pair:        1098240
High Card:       1302540
Sum:             2598960

The number of Five Card Hands and the Sum Matches

Looking good.

And that's it.