3

I have managed to create an algorithm to check the rank of a poker hand. It works 100% correctly, but it's very slow. I've been analysing the code, and the check straight function is one of the slowest parts of it.

So my question is, is there a better way of calculating whether a hand make a straight?

Here is some details:

7 cards, 2 from holder, 5 from board. A can be high or low.

Each card is assigned a value: 2 = 2 3 = 3 .. 9 = 9 T = 10 J = 11 Q = 12 K = 13 A = 14

The script has an array of all 7 cards:

$cards = array(12,5,6,7,4,11,3);

So now I need to be able to sort this into an array where it:

  • discards duplicates
  • orders the card from lowest to highest
  • only returns 5 consecutive cards I.e. (3,4,5,6,7)

It needs to be fast; loops and iterations are very costly. This is what I currently use and when it tries to analyse say 15000 hands, it takes its toll on the script.

For the above, I used:

  • discard duplicates (use array_unique)
  • order cards from lowest to highest (use sort())
  • only return 5 consecutive cards (use a for loop to check the values of cards)

Does anyone have any examples of how I could improve on this? Maybe even in another language that I could perhaps look at and see how it's done?

David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
Patchesoft
  • 317
  • 6
  • 21
  • http://kukuruku.co/hub/php/benchmarks-14-sorting-algorithms-and-php-arrays -> I don't think you will go quicker with the sorting part. Though, I would get the array_unique out. With he number of combinaison you have and 'only' 7 cards, I would say the effort to remove the duplicate is going to cost you more than looping over them. But that has to be benchmarked. – β.εηοιτ.βε Oct 01 '15 at 21:16
  • 1
    When you check if 5 cards are consecutive, do you step 5 cards further and check them backwards to reduce the loop counts ? – Maxime Oct 01 '15 at 21:19
  • You're sorting the array twice: 1. `array_unique()` sorts the array internally, and then returns the values in the original unsorted order. 2. You then re-sort the array. I suggest @Philipp's answer since it involves one sort operation, and does away with `array_unique()` entirely. – Sammitch Oct 01 '15 at 22:17
  • hofan41's bitmap approach is probably the best, but don't forget to add the wheel fix. – Lee Daniel Crocker Oct 02 '15 at 16:41

4 Answers4

4

Instead of working with array deduping and sorting, consider using a bitmask instead, and setting bits to 1 where the card value is set. A bitmask works like a Set datastructure and comes with additional advantages when it comes to detecting contiguous elements.

for ($i = 0; $i < count($cards); $i++) {
    $card = $cards[$i];
    // For each card value, set the bit
    if ($card == 14) {
        // If card is an ace, also set bit 1 for wheel
        $cardBitmask |= 0x2;
    }
    $cardBitmask |= (1 << $card);
}

// To compare, you simply write a for loop checking for 5 consecutive bits
for($i = 10; $i > 0; $i--)
{
    if ($cardBitmask & (0x1F << $i) == (0x1F << $i)) {
        // Straight $i high was found!
    }
} 
hofan41
  • 1,438
  • 1
  • 11
  • 25
  • `$cards.count()` is not valid PHP. – Sammitch Oct 01 '15 at 22:26
  • Also, you need to special-case the wheel (A2345). – Lee Daniel Crocker Oct 02 '15 at 16:26
  • For the wheel, add `if ($cardBitmask == 0x403C)...` – Lee Daniel Crocker Oct 02 '15 at 16:33
  • Or when setting the bitmask and you detect an ace, you can also set bit 1 along with bit 14. – hofan41 Oct 02 '15 at 17:05
  • Yep. Then your for loop would be `for ($i = 10; i > 0; $i--)`. (There are 10 different straights). – Lee Daniel Crocker Oct 02 '15 at 17:08
  • This answer is probably what I'm looking for but for someone who has never used bitmasks before, I'm having trouble understanding the code and some of the operations. Is it possible to rewrite this in a more understandable way for a beginning on bitmasks? – Patchesoft Oct 03 '15 at 15:11
  • Unfortunately this is the easiest way to rewrite this, there is no way I can do bitwise operations in a simpler way. The concept is straightforward, a bitmask in this situation is simply an array of bits where we are indexing into the card value and setting to 1 when the value is present, and 0 when the value is not. Then we can run a bitwise AND operation to detect whether there are 5 bits set to 1 in a row. – hofan41 Oct 05 '15 at 17:44
  • Thanks a lot! I had a look at bitmasks and how they work, then applied some echoing to the values just to see how it was all working and now I understand it. I think I'm going to rewrite my algorithm to completely use bit-masks. – Patchesoft Oct 06 '15 at 10:16
  • One thing though: is there a reason for adding the value to the bitmask by using hexadecimal? I.e. $mask |= 0x2; instead of $mask |= 2; – Patchesoft Oct 06 '15 at 10:17
  • No reason, they are interchangeable, just a habit of mine to specify hex when working with bits. – hofan41 Oct 06 '15 at 14:57
1

Consider the Java implementation at this link. I've included it here:

public static boolean isStraight( Card[] h )
{
  int i, testRank;

  if ( h.length != 5 )
     return(false);

  sortByRank(h);      // Sort the poker hand by the rank of each card      

  /* ===========================
     Check if hand has an Ace
     =========================== */
  if ( h[4].rank() == 14 )
  {
     /* =================================
        Check straight using an Ace
        ================================= */
     boolean a = h[0].rank() == 2 && h[1].rank() == 3 &&
                 h[2].rank() == 4 && h[3].rank() == 5 ;
     boolean b = h[0].rank() == 10 && h[1].rank() == 11 &&        
                 h[2].rank() == 12 && h[3].rank() == 13 ;

     return ( a || b );
  }
  else
  {
     /* ===========================================
        General case: check for increasing values
        =========================================== */
     testRank = h[0].rank() + 1;

     for ( i = 1; i < 5; i++ )
     {
        if ( h[i].rank() != testRank )
           return(false);        // Straight failed...

        testRank++;   // Next card in hand
     }

     return(true);        // Straight found !
  }
}

A quick Google search for "check for poker straight (desired_lang)" will give you other implementations.

pkshultz
  • 409
  • 1
  • 3
  • 16
1

You could just sort the cards and loop over them in an array - saving always the last card and compare them with the current one.

$cards = array(12,5,6,7,4,11,3);
sort($cards);

$last = 0;
$count = 0;
$wheel = false;
foreach ($cards as $card) {
    if ($card == $last) {
        continue;
    } else if ($card == ++$last) {
        $count++;
    } else {
        if ($last == 6) $wheel = true;
        $count = 1;
        $last = $card;
    }

    if ($count == 5 || ($card == 14 && $wheel)) {
        echo "straight $last";
        $straight = range($last - 4, $last);
        break;
    }
}
Philipp
  • 15,377
  • 4
  • 35
  • 52
0

You may go like this, you don't need to sort or anything (assuming that 2 is 2 and 14 is ace):

$cards = [12,5,6,7,4,11,3];

function _inc(&$i) {
    if ($i == 14) 
        $i = 2;
    else
        $i++;
    return $i;
}

$straight = false;
for($i = 2; $i <= 14; $i++) {
    $ind = $i;
    if (!in_array($ind, $cards)) continue;
    $s = [$ind, _inc($ind), _inc($ind), _inc($ind), _inc($ind)];    
    $straight = count(array_intersect($s, $cards)) == count($s);
    if ($straight) break;
}

print $straight;
Timofey
  • 823
  • 1
  • 8
  • 26