Ranking poker hands: Difference between revisions

From APL Wiki
Jump to navigation Jump to search
m (Text replacement - "</source>" to "</syntaxhighlight>")
Tags: Mobile edit Mobile web edit
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This article uses [[Dyalog APL]] to rank poker hands using the rules of [[wikipedia:Texas hold 'em|Texas hold 'em]] and other popular poker variants. The function <source lang=apl inline>handrank</source> below transforms a representation of a poker hand into a "rank" array so that the ordering of ranks under [[total array ordering]] is the same as the ordering of the initial poker hands. Poker hands can then by sorted or compared by calling <source lang=apl inline>handrank</source> on each one, then ordering with normal APL functions.
This article uses [[Dyalog APL]] to rank poker hands using the rules of [[wikipedia:Texas hold 'em|Texas hold 'em]] and other popular poker variants. The function <syntaxhighlight lang=apl inline>handrank</syntaxhighlight> below transforms a representation of a poker hand into a "rank" array so that the ordering of ranks under [[total array ordering]] is the same as the ordering of the initial poker hands. Poker hands can then by sorted or compared by calling <syntaxhighlight lang=apl inline>handrank</syntaxhighlight> on each one, then ordering with normal APL functions.


== Dyalog '19 version ==
== Dyalog '19 version ==


[[Marshall Lochbaum]] showed the following code in a presentation (D04: "Tacit Techniques with Dyalog version 18.0 Operators"; video [https://dyalog.tv/Dyalog19/?v=czWC4tjwzOQ here]) at the [https://www.dyalog.com/user-meetings/dyalog19.htm Dyalog '19] user meeting.
[[Marshall Lochbaum]] showed the following code in a presentation (D04: "Tacit Techniques with Dyalog version 18.0 Operators"; video [https://dyalog.tv/Dyalog19/?v=czWC4tjwzOQ here]) at the [https://www.dyalog.com/user-meetings/dyalog19.htm Dyalog '19] user meeting.
<source lang=apl>
<syntaxhighlight lang=apl>
handrank←{⊃{((8|@1⊢2-/⍺)⍵∧.=¨1,⊃⍵){(∧/⍺),(2=≢⍵),⍺,⊂⍵[⍒⍵;]⊖⍨⊃⍺},⍨∘≢⌸⍺}/↓⍉⍵[⍒⍵;]}
handrank←{⊃{((8|@1⊢2-/⍺)⍵∧.=¨1,⊃⍵){(∧/⍺),(2=≢⍵),⍺,⊂⍵[⍒⍵;]⊖⍨⊃⍺},⍨∘≢⌸⍺}/↓⍉⍵[⍒⍵;]}
cmp ← {⍺⍵-⍥(⊃⍋)⍵⍺}
cmp ← {⍺⍵-⍥(⊃⍋)⍵⍺}
cmphand ← cmp ⍥ handrank
cmphand ← cmp ⍥ handrank
</source>
</syntaxhighlight>
The <source lang=apl inline>cmphand</source> function compares two hands to yield ¯1 if the left hand is better, 1 if the right is better, and 0 if they are tied. In the talk Marshall calls it by inverting a display function (a task which Dyalog's [[Inverse]] cannot yet handle):
The <syntaxhighlight lang=apl inline>cmphand</syntaxhighlight> function compares two hands to yield ¯1 if the left hand is better, 1 if the right is better, and 0 if they are tied. In the talk Marshall calls it by inverting a display function (a task which Dyalog's [[Inverse]] cannot yet handle):
<source lang=apl>
<syntaxhighlight lang=apl>
       I ← ⌷⍤0 99
       I ← ⌷⍤0 99
       disp ← I¨ ∘ ((2↓⎕D),'TJQKA')('♣♢♡♠') ⍤ 1
       disp ← I¨ ∘ ((2↓⎕D),'TJQKA')('♣♢♡♠') ⍤ 1
       (5 2⍴'6♡6♣6♢K♡K♠') cmphand⍥(disp⍣¯1) (5 2⍴'A♠2♠3♣4♠5♢')
       (5 2⍴'6♡6♣6♢K♡K♠') cmphand⍥(disp⍣¯1) (5 2⍴'A♠2♠3♣4♠5♢')
¯1
¯1
</source>
</syntaxhighlight>
These functions demonstrate the usefulness of [[Over]], but the <source lang=apl inline>handrank</source> function doesn't depend on any new features from Dyalog 18.0.
These functions demonstrate the usefulness of [[Over]], but the <syntaxhighlight lang=apl inline>handrank</syntaxhighlight> function doesn't depend on any new features from Dyalog 18.0.


The implementation of <source lang=apl inline>handrank</source> above has an error: it ranks flushes above straights. This can be fixed by [[Reverse|reversing]] in two places:
The implementation of <syntaxhighlight lang=apl inline>handrank</syntaxhighlight> above has an error: it ranks flushes above straights. This can be fixed by [[Reverse|reversing]] in two places:
<source lang=apl>
<syntaxhighlight lang=apl>
handrank←{⊃{(⌽(8|@1⊢2-/⍺)⍵∧.=¨1,⊃⍵){(∧/⍺),(2=≢⍵),⍺,⊂⍵[⍒⍵;]⊖⍨⊃⌽⍺},⍨∘≢⌸⍺}/↓⍉⍵[⍒⍵;]}
handrank←{⊃{(⌽(8|@1⊢2-/⍺)⍵∧.=¨1,⊃⍵){(∧/⍺),(2=≢⍵),⍺,⊂⍵[⍒⍵;]⊖⍨⊃⌽⍺},⍨∘≢⌸⍺}/↓⍉⍵[⍒⍵;]}
</source>
</syntaxhighlight>


== Displaying and parsing hands ==
== Displaying and parsing hands ==


We will describe each card with an [[index]] for its rank (two to ace) and another index for its suit (clubs to spades). The function <source lang=apl inline>disp</source> below displays a single card by [[Pick|picking]] the appropriate character for suit and rank. We use the character <source lang=apl inline>'T'</source> for ten.
We will describe each card with an [[index]] for its rank (two to ace) and another index for its suit (clubs to spades). The function <syntaxhighlight lang=apl inline>disp</syntaxhighlight> below displays a single card by [[Pick|picking]] the appropriate character for suit and rank. We use the character <syntaxhighlight lang=apl inline>'T'</syntaxhighlight> for ten.
<source lang=apl>
<syntaxhighlight lang=apl>
       disp ← ⊃¨ ∘ ((2↓⎕D),'TJQKA')('♣♢♡♠')
       disp ← ⊃¨ ∘ ((2↓⎕D),'TJQKA')('♣♢♡♠')
       disp 1 1
       disp 1 1
Line 32: Line 32:
       disp⍣¯1 ⊢'2♣'
       disp⍣¯1 ⊢'2♣'
1 1
1 1
</source>
</syntaxhighlight>
Because <source lang=apl inline>disp</source> is invertible, we can also use it to read a single card, as shown in the last line above. We can parse a hand of five cards, written as a single [[string]], by [[Reshape|reshaping]] it to [[shape]] 5 2, and undisplaying each row ([[Rank operator|rank]] 1).
Because <syntaxhighlight lang=apl inline>disp</syntaxhighlight> is invertible, we can also use it to read a single card, as shown in the last line above. We can parse a hand of five cards, written as a single [[string]], by [[Reshape|reshaping]] it to [[shape]] 5 2, and undisplaying each row ([[Rank operator|rank]] 1).
<source lang=apl>
<syntaxhighlight lang=apl>
       (disp⍣¯1⍤1) 5 2⍴'A♠2♠3♣4♠5♢'
       (disp⍣¯1⍤1) 5 2⍴'A♠2♠3♣4♠5♢'
13 4
13 4
Line 42: Line 42:
  4 2
  4 2
       hand ← {(disp⍣¯1⍤1) 5 2⍴⍵}
       hand ← {(disp⍣¯1⍤1) 5 2⍴⍵}
</source>
</syntaxhighlight>


== Hand rank ==
== Hand rank ==


In poker ranking, the highest card in the hand is the most important. We start our ranking by placing the highest card first, that is, sorting in descending order:
In poker ranking, the highest card in the hand is the most important. We start our ranking by placing the highest card first, that is, sorting in descending order:
<source lang=apl>
<syntaxhighlight lang=apl>
       sort ← {(⊂⍒⍵)⌷⍵}
       sort ← {(⊂⍒⍵)⌷⍵}
       sort hand 'A♠2♠3♣4♠5♢'
       sort hand 'A♠2♠3♣4♠5♢'
Line 55: Line 55:
  2 1
  2 1
  1 4
  1 4
</source>
</syntaxhighlight>
However, hands are sorted not just by the highest card but by how many of each rank there are. For example, two twos in a hand are more important than a single king. We take this into account by grouping equal ranks together with the [[Key]] operator. In the operand to Key, we place the number of cards of each rank (<source lang=apl inline>(≢⍵)</source>) before the number of the rank (<source lang=apl inline>⍺</source>). Then we sort in descending order again, to put the ranks that appear the most times first. For this part of the ranking algorithm the cards didn't need to be sorted initially, but we'll use the initial sorting above later.
However, hands are sorted not just by the highest card but by how many of each rank there are. For example, two twos in a hand are more important than a single king. We take this into account by grouping equal ranks together with the [[Key]] operator. In the operand to Key, we place the number of cards of each rank (<syntaxhighlight lang=apl inline>(≢⍵)</syntaxhighlight>) before the number of the rank (<syntaxhighlight lang=apl inline>⍺</syntaxhighlight>). Then we sort in descending order again, to put the ranks that appear the most times first. For this part of the ranking algorithm the cards didn't need to be sorted initially, but we'll use the initial sorting above later.
<source lang=apl>
<syntaxhighlight lang=apl>
       ⊣/ sort hand '6♡6♣6♢K♡K♠'
       ⊣/ sort hand '6♡6♣6♢K♡K♠'
12 12 5 5 5
12 12 5 5 5
Line 63: Line 63:
2 12
2 12
3  5
3  5
</source>
</syntaxhighlight>


=== Straights and flushes ===
=== Straights and flushes ===


The small amount of code above is enough to rank any hands if we ignore the rules for straights (five ranks in a row) and flushes (all five cards of the same suit). However, poker has some complicated rules for these types of hands which we must take into account. We begin by splitting the hand into ranks <source lang=apl inline>r</source> and suits <source lang=apl inline>s</source>:
The small amount of code above is enough to rank any hands if we ignore the rules for straights (five ranks in a row) and flushes (all five cards of the same suit). However, poker has some complicated rules for these types of hands which we must take into account. We begin by splitting the hand into ranks <syntaxhighlight lang=apl inline>r</syntaxhighlight> and suits <syntaxhighlight lang=apl inline>s</syntaxhighlight>:
<source lang=apl>
<syntaxhighlight lang=apl>
       r s ← ↓⍉sort hand 'A♠2♠3♣4♠5♢'
       r s ← ↓⍉sort hand 'A♠2♠3♣4♠5♢'
</source>
</syntaxhighlight>
First we compute whether the hand is a flush, a decision that depends only on the suits. If every suit is equal to the [[first]] one, then the hand is a flush.
First we compute whether the hand is a flush, a decision that depends only on the suits. If every suit is equal to the [[first]] one, then the hand is a flush.
<source lang=apl>
<syntaxhighlight lang=apl>
       s
       s
4 2 4 1 4
4 2 4 1 4
Line 79: Line 79:
       ∧/s=⊃s
       ∧/s=⊃s
0
0
</source>
</syntaxhighlight>
Now we determine whether the hand is a straight. We can find the difference from each card's rank to the next highest (recall that the ranks are sorted descending) using a pair-wise difference, a kind of [[windowed reduction]]. Ordinarily, a hand is a straight if each difference is exactly one. However, there is an additional complication in that an ace (the highest rank; 13 here) may come before a two in a straight—it can be treated as the lowest rank if this improves the hand. To account for this we also allow the difference between the highest and next-highest card to be 9, the difference between ace and five. If this difference is 9 and each other difference is 1, then the ranks must be ace, five, four, three, and two—a straight. To allow both 1 and 9 for the first value, we use the [[residue]] on division by 8. Residue is applied to only the first difference using the [[At]] operator.
Now we determine whether the hand is a straight. We can find the difference from each card's rank to the next highest (recall that the ranks are sorted descending) using a pair-wise difference, a kind of [[windowed reduction]]. Ordinarily, a hand is a straight if each difference is exactly one. However, there is an additional complication in that an ace (the highest rank; 13 here) may come before a two in a straight—it can be treated as the lowest rank if this improves the hand. To account for this we also allow the difference between the highest and next-highest card to be 9, the difference between ace and five. If this difference is 9 and each other difference is 1, then the ranks must be ace, five, four, three, and two—a straight. To allow both 1 and 9 for the first value, we use the [[residue]] on division by 8. Residue is applied to only the first difference using the [[At]] operator.
<source lang=apl>
<syntaxhighlight lang=apl>
       r
       r
13 4 3 2 1
13 4 3 2 1
Line 90: Line 90:
       1 ∧.= 8|@1⊢2-/r
       1 ∧.= 8|@1⊢2-/r
1
1
</source>
</syntaxhighlight>
The rank and suit combination can be combined to save characters (an important consideration when presenting with limited screen width!). Each compares two arrays using scalar [[Equals]] and combines the results with [[And]]. Rather than writing this computation twice we can merge the two sides of each computation together and use [[Each]].
The rank and suit combination can be combined to save characters (an important consideration when presenting with limited screen width!). Each compares two arrays using scalar [[Equals]] and combines the results with [[And]]. Rather than writing this computation twice we can merge the two sides of each computation together and use [[Each]].
<source lang=apl>
<syntaxhighlight lang=apl>
       (8|@1⊢2-/r)s
       (8|@1⊢2-/r)s
┌───────┬─────────┐
┌───────┬─────────┐
Line 105: Line 105:
       (8|@1⊢2-/r)s∧.=¨1,⊃s
       (8|@1⊢2-/r)s∧.=¨1,⊃s
1 0
1 0
</source>
</syntaxhighlight>
The choice of the [[inner product]] <source lang=apl inline>∧.=</source> over a reduction is entirely stylistic.
The choice of the [[inner product]] <syntaxhighlight lang=apl inline>∧.=</syntaxhighlight> over a reduction is entirely stylistic.


=== Final ranking ===
=== Final ranking ===


Taking a sample hand, let's compute the ranks and a boolean indicator for whether it's a straight and/or flush.
Taking a sample hand, let's compute the ranks and a boolean indicator for whether it's a straight and/or flush.
<source lang=apl>
<syntaxhighlight lang=apl>
       r s ← ↓⍉sort hand 'A♠2♠3♣4♠5♢'
       r s ← ↓⍉sort hand 'A♠2♠3♣4♠5♢'
       ⊢ranks ← sort {(≢⍵),⍺}⌸ r
       ⎕←ranks ← sort {(≢⍵),⍺}⌸ r
1 13
1 13
1  4
1  4
Line 119: Line 119:
1  2
1  2
1  1
1  1
       ⊢sf ← (8|@1⊢2-/r)s∧.=¨1,⊃s
       ⎕←sf ← (8|@1⊢2-/r)s∧.=¨1,⊃s
1 0
1 0
</source>
</syntaxhighlight>
The final rank is now a combination of four arrays. At the end we will compare hands based on the number of each rank involved, but first we will modify this ranking based on some features that make certain hands more valuable than others. A straight flush is the highest-ranked hand, followed by a four of a kind and a full house (three of one rank and two of another). We can quickly test whether a hand has one of the latter combinations by testing whether there are exactly two different ranks present—if so, they must be split 4-1 or 3-2. After these, flushes and then straights are ranked higher than other hands.
The final rank is now a combination of four arrays. At the end we will compare hands based on the number of each rank involved, but first we will modify this ranking based on some features that make certain hands more valuable than others. A straight flush is the highest-ranked hand, followed by a four of a kind and a full house (three of one rank and two of another). We can quickly test whether a hand has one of the latter combinations by testing whether there are exactly two different ranks present—if so, they must be split 4-1 or 3-2. After these, flushes and then straights are ranked higher than other hands.
<source lang=apl>
<syntaxhighlight lang=apl>
       ∧/sf                  ⍝ Is it a straight flush?
       ∧/sf                  ⍝ Is it a straight flush?
0
0
Line 136: Line 136:
1  1
1  1
1 13
1 13
</source>
</syntaxhighlight>
Within each category based on the first three special values, hands are ranked by card ranks. Suits are never used for comparison: they only affect whether the hand is a straight.
Within each category based on the first three special values, hands are ranked by card ranks. Suits are never used for comparison: they only affect whether the hand is a straight.


Line 143: Line 143:
== Complete function ==
== Complete function ==


<source lang=apl>
<syntaxhighlight lang=apl>
handrank ← {
handrank ← {
     sort ← {(⊂⍒⍵)⌷⍵}                      ⍝ Sort descending idiom
     sort ← {(⊂⍒⍵)⌷⍵}                      ⍝ Sort descending idiom
Line 151: Line 151:
     (∧/sf),(2=≢ranks),(⌽sf),⊂(⊃sf)⊖ranks  ⍝ Final rank
     (∧/sf),(2=≢ranks),(⌽sf),⊂(⊃sf)⊖ranks  ⍝ Final rank
}
}
</source>
</syntaxhighlight>


== Examples ==
== Examples ==


To test our function, we can try it on the sample hands given in Wikipedia's page on Texas hold 'em [[wikipedia:Texas hold 'em#Hand_values|here]]. The ranks for each of these hands are shown, as well as a [[grade up]] to demonstrate that they are in ascending order.
To test our function, we can try it on the sample hands given in Wikipedia's page on Texas hold 'em [[wikipedia:Texas hold 'em#Hand_values|here]]. The ranks for each of these hands are shown, as well as a [[grade up]] to demonstrate that they are in ascending order.
<source lang=apl>
<syntaxhighlight lang=apl>
       hands ← ' '(≠⊆⊢)'A♣4♡7♢K♣2♠ K♣K♡7♢2♣5♠ K♣K♡7♢7♣5♠ K♣K♡K♢7♣5♠ 3♣4♡5♢6♣7♠ K♣Q♣9♣8♣2♣ K♣K♡K♢7♣7♠ K♣K♡K♢K♠5♠ 3♣4♣5♣6♣7♣ T♡J♡Q♡K♡A♡'
       hands ← ' '(≠⊆⊢)'A♣4♡7♢K♣2♠ K♣K♡7♢2♣5♠ K♣K♡7♢7♣5♠ K♣K♡K♢7♣5♠ 3♣4♡5♢6♣7♠ K♣Q♣9♣8♣2♣ K♣K♡K♢7♣7♠ K♣K♡K♢K♠5♠ 3♣4♣5♣6♣7♣ T♡J♡Q♡K♡A♡'
       ↑hands
       ↑hands
Line 226: Line 226:
       ⍋ handrank∘hand¨ hands
       ⍋ handrank∘hand¨ hands
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
</source>
</syntaxhighlight>
 
[[Category:Examples]][[Category:Dyalog APL examples]]

Latest revision as of 22:22, 10 September 2022

This article uses Dyalog APL to rank poker hands using the rules of Texas hold 'em and other popular poker variants. The function handrank below transforms a representation of a poker hand into a "rank" array so that the ordering of ranks under total array ordering is the same as the ordering of the initial poker hands. Poker hands can then by sorted or compared by calling handrank on each one, then ordering with normal APL functions.

Dyalog '19 version

Marshall Lochbaum showed the following code in a presentation (D04: "Tacit Techniques with Dyalog version 18.0 Operators"; video here) at the Dyalog '19 user meeting.

handrank←{⊃{((8|@1⊢2-/⍺)⍵∧.=¨1,⊃⍵){(∧/⍺),(2=≢⍵),⍺,⊂⍵[⍒⍵;]⊖⍨⊃⍺},⍨∘≢⌸⍺}/↓⍉⍵[⍒⍵;]}
cmp ← {⍺⍵-⍥(⊃⍋)⍵⍺}
cmphand ← cmp ⍥ handrank

The cmphand function compares two hands to yield ¯1 if the left hand is better, 1 if the right is better, and 0 if they are tied. In the talk Marshall calls it by inverting a display function (a task which Dyalog's Inverse cannot yet handle):

      I ← ⌷⍤0 99
      disp ← I¨ ∘ ((2↓⎕D),'TJQKA')('♣♢♡♠') ⍤ 1
      (5 2⍴'6♡6♣6♢K♡K♠') cmphand⍥(disp⍣¯1) (5 2⍴'A♠2♠3♣4♠5♢')
¯1

These functions demonstrate the usefulness of Over, but the handrank function doesn't depend on any new features from Dyalog 18.0.

The implementation of handrank above has an error: it ranks flushes above straights. This can be fixed by reversing in two places:

handrank←{⊃{(⌽(8|@1⊢2-/⍺)⍵∧.=¨1,⊃⍵){(∧/⍺),(2=≢⍵),⍺,⊂⍵[⍒⍵;]⊖⍨⊃⌽⍺},⍨∘≢⌸⍺}/↓⍉⍵[⍒⍵;]}

Displaying and parsing hands

We will describe each card with an index for its rank (two to ace) and another index for its suit (clubs to spades). The function disp below displays a single card by picking the appropriate character for suit and rank. We use the character 'T' for ten.

      disp ← ⊃¨ ∘ ((2↓⎕D),'TJQKA')('♣♢♡♠')
      disp 1 1
2♣
      disp⍣¯1 ⊢'2♣'
1 1

Because disp is invertible, we can also use it to read a single card, as shown in the last line above. We can parse a hand of five cards, written as a single string, by reshaping it to shape 5 2, and undisplaying each row (rank 1).

      (disp⍣¯1⍤1) 5 2⍴'A♠2♠3♣4♠5♢'
13 4
 1 4
 2 1
 3 4
 4 2
      hand ← {(disp⍣¯1⍤1) 5 2⍴⍵}

Hand rank

In poker ranking, the highest card in the hand is the most important. We start our ranking by placing the highest card first, that is, sorting in descending order:

      sort ← {(⊂⍒⍵)⌷⍵}
      sort hand 'A♠2♠3♣4♠5♢'
13 4
 4 2
 3 4
 2 1
 1 4

However, hands are sorted not just by the highest card but by how many of each rank there are. For example, two twos in a hand are more important than a single king. We take this into account by grouping equal ranks together with the Key operator. In the operand to Key, we place the number of cards of each rank ((≢⍵)) before the number of the rank (). Then we sort in descending order again, to put the ranks that appear the most times first. For this part of the ranking algorithm the cards didn't need to be sorted initially, but we'll use the initial sorting above later.

      ⊣/ sort hand '6♡6♣6♢K♡K♠'
12 12 5 5 5
      {(≢⍵),⍺}⌸ ⊣/ sort hand '6♡6♣6♢K♡K♠'
2 12
3  5

Straights and flushes

The small amount of code above is enough to rank any hands if we ignore the rules for straights (five ranks in a row) and flushes (all five cards of the same suit). However, poker has some complicated rules for these types of hands which we must take into account. We begin by splitting the hand into ranks r and suits s:

      r s ← ↓⍉sort hand 'A♠2♠3♣4♠5♢'

First we compute whether the hand is a flush, a decision that depends only on the suits. If every suit is equal to the first one, then the hand is a flush.

      s
4 2 4 1 4
      s=⊃s
1 0 1 0 1
      ∧/s=⊃s
0

Now we determine whether the hand is a straight. We can find the difference from each card's rank to the next highest (recall that the ranks are sorted descending) using a pair-wise difference, a kind of windowed reduction. Ordinarily, a hand is a straight if each difference is exactly one. However, there is an additional complication in that an ace (the highest rank; 13 here) may come before a two in a straight—it can be treated as the lowest rank if this improves the hand. To account for this we also allow the difference between the highest and next-highest card to be 9, the difference between ace and five. If this difference is 9 and each other difference is 1, then the ranks must be ace, five, four, three, and two—a straight. To allow both 1 and 9 for the first value, we use the residue on division by 8. Residue is applied to only the first difference using the At operator.

      r
13 4 3 2 1
      2-/r
9 1 1 1
      8|@1⊢2-/r
1 1 1 1
      1 ∧.= 8|@1⊢2-/r
1

The rank and suit combination can be combined to save characters (an important consideration when presenting with limited screen width!). Each compares two arrays using scalar Equals and combines the results with And. Rather than writing this computation twice we can merge the two sides of each computation together and use Each.

      (8|@1⊢2-/r)s
┌───────┬─────────┐
│1 1 1 1│4 2 4 1 4│
└───────┴─────────┘
      (8|@1⊢2-/r)s = 1,⊃s
┌───────┬─────────┐
│1 1 1 1│1 0 1 0 1│
└───────┴─────────┘
      ∧/¨(8|@1⊢2-/r)s=1,⊃s
1 0
      (8|@1⊢2-/r)s∧.=¨1,⊃s
1 0

The choice of the inner product ∧.= over a reduction is entirely stylistic.

Final ranking

Taking a sample hand, let's compute the ranks and a boolean indicator for whether it's a straight and/or flush.

      r s ← ↓⍉sort hand 'A♠2♠3♣4♠5♢'
      ⎕←ranks ← sort {(≢⍵),⍺}⌸ r
1 13
1  4
1  3
1  2
1  1
      ⎕←sf ← (8|@1⊢2-/r)s∧.=¨1,⊃s
1 0

The final rank is now a combination of four arrays. At the end we will compare hands based on the number of each rank involved, but first we will modify this ranking based on some features that make certain hands more valuable than others. A straight flush is the highest-ranked hand, followed by a four of a kind and a full house (three of one rank and two of another). We can quickly test whether a hand has one of the latter combinations by testing whether there are exactly two different ranks present—if so, they must be split 4-1 or 3-2. After these, flushes and then straights are ranked higher than other hands.

      ∧/sf                  ⍝ Is it a straight flush?
0
      2=≢ranks              ⍝ Is it a four of a kind or full house?
0
      ⌽sf                   ⍝ Flushes before straights
0 1
      (⊃sf)⊖ranks           ⍝ Treat high card as low for straights
1  4
1  3
1  2
1  1
1 13

Within each category based on the first three special values, hands are ranked by card ranks. Suits are never used for comparison: they only affect whether the hand is a straight.

To order straights correctly, we must take into account ace-low straights. In such straights five is considered the high card. We fix the ordering by rotating all straights to compare starting at the second-highest card.

Complete function

handrank ← {
    sort ← {(⊂⍒⍵)⌷⍵}                      ⍝ Sort descending idiom
    r s←↓⍉sort ⍵                          ⍝ Rank and suit after sorting
    ranks ← sort {(≢⍵),⍺}⌸ r              ⍝ Number of each rank
    sf ← (8|@1⊢2-/r)s∧.=¨1,⊃s             ⍝ Is straight, is flush
    (∧/sf),(2=≢ranks),(⌽sf),⊂(⊃sf)⊖ranks  ⍝ Final rank
}

Examples

To test our function, we can try it on the sample hands given in Wikipedia's page on Texas hold 'em here. The ranks for each of these hands are shown, as well as a grade up to demonstrate that they are in ascending order.

      hands ← ' '(≠⊆⊢)'A♣4♡7♢K♣2♠ K♣K♡7♢2♣5♠ K♣K♡7♢7♣5♠ K♣K♡K♢7♣5♠ 3♣4♡5♢6♣7♠ K♣Q♣9♣8♣2♣ K♣K♡K♢7♣7♠ K♣K♡K♢K♠5♠ 3♣4♣5♣6♣7♣ T♡J♡Q♡K♡A♡'
      ↑hands
A♣4♡7♢K♣2♠
K♣K♡7♢2♣5♠
K♣K♡7♢7♣5♠
K♣K♡K♢7♣5♠
3♣4♡5♢6♣7♠
K♣Q♣9♣8♣2♣
K♣K♡K♢7♣7♠
K♣K♡K♢K♠5♠
3♣4♣5♣6♣7♣
T♡J♡Q♡K♡A♡
      {⍵,↑handrank∘hand¨⍵} hands
┌──────────┬─┬─┬─┬─┬────┐
│A♣4♡7♢K♣2♠│0│0│0│0│1 13│
│          │ │ │ │ │1 12│
│          │ │ │ │ │1  6│
│          │ │ │ │ │1  3│
│          │ │ │ │ │1  1│
├──────────┼─┼─┼─┼─┼────┤
│K♣K♡7♢2♣5♠│0│0│0│0│2 12│
│          │ │ │ │ │1  6│
│          │ │ │ │ │1  4│
│          │ │ │ │ │1  1│
├──────────┼─┼─┼─┼─┼────┤
│K♣K♡7♢7♣5♠│0│0│0│0│2 12│
│          │ │ │ │ │2  6│
│          │ │ │ │ │1  4│
├──────────┼─┼─┼─┼─┼────┤
│K♣K♡7♢7♣5♠│0│0│0│0│2 12│
│          │ │ │ │ │2  6│
│          │ │ │ │ │1  4│
├──────────┼─┼─┼─┼─┼────┤
│K♣K♡K♢7♣5♠│0│0│0│0│3 12│
│          │ │ │ │ │1  6│
│          │ │ │ │ │1  4│
├──────────┼─┼─┼─┼─┼────┤
│3♣4♡5♢6♣7♠│0│0│0│1│1 5 │
│          │ │ │ │ │1 4 │
│          │ │ │ │ │1 3 │
│          │ │ │ │ │1 2 │
│          │ │ │ │ │1 6 │
├──────────┼─┼─┼─┼─┼────┤
│K♣Q♣9♣8♣2♣│0│0│1│0│1 12│
│          │ │ │ │ │1 11│
│          │ │ │ │ │1  8│
│          │ │ │ │ │1  7│
│          │ │ │ │ │1  1│
├──────────┼─┼─┼─┼─┼────┤
│K♣K♡K♢7♣7♠│0│1│0│0│3 12│
│          │ │ │ │ │2  6│
├──────────┼─┼─┼─┼─┼────┤
│K♣K♡K♢K♠5♠│0│1│0│0│4 12│   
│          │ │ │ │ │1  4│
├──────────┼─┼─┼─┼─┼────┤                                                              
│3♣4♣5♣6♣7♣│1│0│1│1│1 5 │       
│          │ │ │ │ │1 4 │
│          │ │ │ │ │1 3 │
│          │ │ │ │ │1 2 │
│          │ │ │ │ │1 6 │
├──────────┼─┼─┼─┼─┼────┤
│T♡J♡Q♡K♡A♡│1│0│1│1│1 12│
│          │ │ │ │ │1 11│
│          │ │ │ │ │1 10│
│          │ │ │ │ │1  9│
│          │ │ │ │ │1 13│
└──────────┴─┴─┴─┴─┴────┘
      ⍋ handrank∘hand¨ hands
1 2 3 4 5 6 7 8 9 10