This is my response to The Weekly Challenge #291.
@ints
.
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
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.
#! /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.