Open Side Menu Go to the Top
Register
PLO Hand Lookup program PLO Hand Lookup program

09-21-2018 , 10:25 AM
There are 270,725 (52C4) starting hands in Omaha, counting suits.

I wrote a program that puts them all in a list.

Hand #1: 2c 3c 4c 5c
Hand #2: 2c 3c 4c 6c
Hand #3: 2c 3c 4c 7c
Hand #4: 2c 3c 4c 8c

………….etc

Hand #48: 2c 3c 4c Ks
Hand #49: 2c 3c 4c As
Hand #50: 2c 3c 5c 6c
Hand #51: 2c 3c 5c 7c

………….etc

Hand #270722: Ts Js Qs As
Hand #270723: Ts Js Ks As
Hand #270724: Ts Qs Ks As
Hand #270725: Js Qs Ks As

Great. So now, how do we do the reverse? Given four random cards, such as (7d 8d Kh Ks) is there an elegant formula that will compute its hand number?

I want to store some statistics for each hand in an array[270725] and then quickly look up a hand.
PLO Hand Lookup program Quote
09-21-2018 , 12:30 PM
I feel like there has to be a way to do this, but I spent a little time this morning and came up fairly blank, in terms of a straightforward numeric algorithm

Can you do me a favor and send me your hand list? I could generate it myself but it would save me some time. I have a few ideas.

I assume you'll be using C/C++? If not I'd say just use a hashmap.
PLO Hand Lookup program Quote
09-21-2018 , 01:20 PM
Here's the code, is that what you wanted? Or do you want a file with all 270725 hands in it?

public function EnumerateOmahaHands()
{
var totalHands = 0;
for (var c0 = 0; c0 < 1; c0++)
{
for (var c1 = c0+1; c1 < 50; c1++)
{
for (var c2 = c1+1; c2 < 51; c2++)
{
for (var c3 = c2+1; c3 < 52; c3++)
{
totalHands++;
trace("HAND #" + totalHands + ": " + GetCard(c0) + " " + GetCard(c1) + " " + GetCard(c2) + " " + GetCard(c3));
}
}
}
}
}

public function GetCard(card:Number)
{
var cardString = "";
var rankArray = "23456789TJQKA";
var suitArray = "cdhs";
cardString += rankArray.charAt(card % 13);
cardString += suitArray.charAt(card / 13);
return cardString;
}
PLO Hand Lookup program Quote
09-21-2018 , 02:04 PM
Code:
// First make a lookup table of all the 24 possible hand ordering permutations:
int PERMS[24][4]={{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1}, {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0}, {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}};

// Next we need a function to map an unordered 4-card hand to a unique integer.
// NOTE: 140608=52^3, 2704=52^2, etc.
int getIndex(int c0, int c1, int c2, int c3) {
  return  c0*140608 + c1*2704 + c2*52 + c3;
}

// Next create a lookup table of size 52^4, which we will populate so as to map from unordered --> ordered index.
int lookup[7311616];

// You can now populate this as you create your ordering:
int handIndex=0;
for (int c0 = 0; c0 < 49; c0++) {	
  for (int c1 = c0+1; c1 < 50; c1++) {
    for (int c2 = c1+1; c2 < 51; c2++) {
      for (int c3 = c2+1; c3 < 52; c3++) {
        int hand[4]={c0, c1, c2, c3};
        for (int i=0; i<24; i++) {
          lookup[getIndex(hand[PERMS[i][0]],hand[PERMS[i][1]],hand[PERMS[i][2]],hand[PERMS[i][3]])]=handIndex;
        }
        handIndex++;
      }
    }
  }
}

.
.
.

// Now you can lookup your ordered hand index in O(1) from an unordered hand using:
int handIndex=lookup[getIndex(c0,c1,c2,c3)];
PS: Be careful if you copy my code exactly and write this in C/C++: putting a ~30MB array on the stack will cause a stack overflow! Either use "static int lookup[7311616];" or allocate it dynamically to ensure it goes on the heap.

Juk

Last edited by jukofyork; 09-21-2018 at 02:19 PM.
PLO Hand Lookup program Quote
09-21-2018 , 02:16 PM
Quote:
Originally Posted by Steve
Code:
public function EnumerateOmahaHands()
{
	var totalHands = 0;
	for (var c0 = 0; c0 < 1; c0++) 
	{		
		for (var c1 = c0+1; c1 < 50; c1++) 
		{
			for (var c2 = c1+1; c2 < 51; c2++) 
			{
				for (var c3 = c2+1; c3 < 52; c3++) 
				{
					totalHands++;
					trace("HAND #" + totalHands + ": " + GetCard(c0) + " " + GetCard(c1) + " " + GetCard(c2) + " " + GetCard(c3)); 
				}
			}
		}
	}
}
Surely that should be 49 or else you'll only generate hands starting with 2c?

Juk
PLO Hand Lookup program Quote
09-21-2018 , 02:47 PM
Quote:
Originally Posted by jukofyork
Surely that should be 49 or else you'll only generate hands starting with 2c?

Juk
Yikes, sorry yep that's what I meant.
PLO Hand Lookup program Quote
09-21-2018 , 02:51 PM
Quote:
Originally Posted by jukofyork
Code:
// First make a lookup table of all the 24 possible hand ordering permutations:
int PERMS[24][4]={{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1}, {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0}, {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}};

// Next we need a function to map an unordered 4-card hand to a unique integer.
// NOTE: 140608=52^3, 2704=52^2, etc.
int getIndex(int c0, int c1, int c2, int c3) {
  return  c0*140608 + c1*2704 + c2*52 + c3;
}

// Next create a lookup table of size 52^4, which we will populate so as to map from unordered --> ordered index.
int lookup[7311616];

// You can now populate this as you create your ordering:
int handIndex=0;
for (int c0 = 0; c0 < 49; c0++) {	
  for (int c1 = c0+1; c1 < 50; c1++) {
    for (int c2 = c1+1; c2 < 51; c2++) {
      for (int c3 = c2+1; c3 < 52; c3++) {
        int hand[4]={c0, c1, c2, c3};
        for (int i=0; i<24; i++) {
          lookup[getIndex(hand[PERMS[i][0]],hand[PERMS[i][1]],hand[PERMS[i][2]],hand[PERMS[i][3]])]=handIndex;
        }
        handIndex++;
      }
    }
  }
}

.
.
.

// Now you can lookup your ordered hand index in O(1) from an unordered hand using:
int handIndex=lookup[getIndex(c0,c1,c2,c3)];
PS: Be careful if you copy my code exactly and write this in C/C++: putting a ~30MB array on the stack will cause a stack overflow! Either use "static int lookup[7311616];" or allocate it dynamically to ensure it goes on the heap.

Juk
Thanks Juk. I'm still hoping there is an elegant formula for looking up a hand# that doesn't involve creating this 7MB table in memory. This is going to be in a JavaScript poker tool and I was hoping to avoid creating that at runtime (or having to download 7MB). But, there may not be a formula.
PLO Hand Lookup program Quote
09-21-2018 , 03:19 PM
yeah so if it's javascript I would just say use a hashmap instead of a numeric array
PLO Hand Lookup program Quote
09-21-2018 , 05:18 PM
Quote:
Originally Posted by Steve
Thanks Juk. I'm still hoping there is an elegant formula for looking up a hand# that doesn't involve creating this 7MB table in memory. This is going to be in a JavaScript poker tool and I was hoping to avoid creating that at runtime (or having to download 7MB). But, there may not be a formula.
Here's a rough outline of a method that requires no lookup table (but is more costly per operation):

Given an unordered 4-tuple:

You first need sort it into ascending order. The most efficient method for this is to use a 4-element sorting network, eg:

Code:
void sortInPlace(int& c0, int& c1, int& c2, int& c3) {
  SWAP_IF_GREATER_THAN(c0,c2);
  SWAP_IF_GREATER_THAN(c1,c3);
  SWAP_IF_GREATER_THAN(c0,c1);
  SWAP_IF_GREATER_THAN(c2,c3);
  SWAP_IF_GREATER_THAN(c1,c2);
}
Then you need to use the idea of combinadics to be able to directly generate a position in the lexicographic ordering, eg:

Code:
int getHandIndex(int c0, int c1, int c2, int c3) {
  return binomial(c0,1) + binomial(c1,2) + binomial(c2,3) + binomial(c3,4);
}
So then you can get your hand index directly like so:

Code:
sortInPlace(c0,c1,c2,c3);
int handIndex=getHandIndex(c0,c1,c2,c3);
(you'll have to search for a javascript implementation of binomial(). In C++ you can use the boost library's binomial_coefficient() function - perhaps have a look at the source for that?)

Juk

Last edited by jukofyork; 09-21-2018 at 05:30 PM.
PLO Hand Lookup program Quote
09-21-2018 , 06:23 PM
Too late to edit now, but I just noticed that the code above doesn't actually generate the indices in the same order as your loops. From the wiki page:

Quote:
So comparing the 5-combinations C = {0,3,4,6,9} and C' = {0,1,3,7,9}, one has that C comes before C', since they have the same largest part 9, but the next largest part 6 of C is less than the next largest part 7 of C'; the sequences compared lexicographically are (9,6,4,3,0) and (9,7,3,1,0)
I *think* this should generate the ordering in exactly the same way as your loops though:

Code:
int getHandIndex(int c0, int c1, int c2, int c3) {
  return 270724 - binomial(52-(c1+1),4) - binomial(52-(c2+1),3) - binomial(52-(c3+1),2) - binomial(52-(c4+1),1);
}
Also, the "National Lottery example in Excel" example shows a slight optimisation as: binomial(52-(c4+1),1) = 51-c4

Code:
int getHandIndex(int c0, int c1, int c2, int c3) {
  return 270673 - binomial(52-(c1+1),4) - binomial(52-(c2+1),3) - binomial(52-(c3+1),2) + c4;
}
Juk

Last edited by jukofyork; 09-21-2018 at 06:28 PM.
PLO Hand Lookup program Quote
09-21-2018 , 07:04 PM
Courtesy of Mathematica's "simplify", here's the final function along with a small test driver:
Code:
#include <iostream>

// NOTE: Must have c0 < c1 < c2 < c3.
int getHandIndex(int c0, int c1, int c2, int c3) {
  return (484902*c0 - 14699*c0*c0 + 198*c0*c0*c0 - c0*c0*c0*c0 + 4*(-7962 + 7499*c1 - 150*c1*c1 + c1*c1*c1 + 303*c2 - 3*c2*c2 + 6*c3))/24;
}

int main()
{
  int handIndex=0;
  for (int c0 = 0; c0 < 49; c0++) {	
    for (int c1 = c0+1; c1 < 50; c1++) {
      for (int c2 = c1+1; c2 < 51; c2++) {
        for (int c3 = c2+1; c3 < 52; c3++) {
          std::cout << handIndex << " = " << getHandIndex(c0,c1,c2,c3) << "\n";
          handIndex++;
        }
      }
    }
  }
}
(just paste it into http://cpp.sh/ and hit "run")

I'm not sure if I'd call it an "elegant formula" as the OP hoped for, but it does seem to work!

Juk
PLO Hand Lookup program Quote
09-21-2018 , 09:17 PM
Quote:
Originally Posted by jukofyork

I'm not sure if I'd call it an "elegant formula" as the OP hoped for, but it does seem to work!

Juk
THAT'S INCREDIBLE!

And yes, I call that elegant x 10^23

I'm reading up on the lottery example now. THANKS JUK!
PLO Hand Lookup program Quote
09-22-2018 , 07:30 AM
Quote:
Originally Posted by Steve
THAT'S INCREDIBLE!

And yes, I call that elegant x 10^23

I'm reading up on the lottery example now. THANKS JUK!
NP

If you search google for 'site:twoplustwo.com combinadics' and 'site:twoplustwo.com "sorting network"' then you'll find some other posts I made (~10 years ago) about using this same method in the "7 Card Hand Evaluators" thread.

Juk
PLO Hand Lookup program Quote
09-26-2018 , 03:57 PM
Quote:
Originally Posted by Steve
I want to store some statistics for each hand in an array[270725] and then quickly look up a hand.
Why not just use a hash table?
PLO Hand Lookup program Quote
09-28-2018 , 11:35 AM
Ehn you create your hands, jsut store them in a hash table...

Code:
public function EnumerateOmahaHands()
{
var hands = {};
var totalHands = 0;
for (var c0 = 0; c0 < 1; c0++) 
{	
for (var c1 = c0+1; c1 < 50; c1++) 
{
for (var c2 = c1+1; c2 < 51; c2++) 
{
for (var c3 = c2+1; c3 < 52; c3++) 
{
totalHands++;
var hand = GetCard(c0) + " " + GetCard(c1) + " " + GetCard(c2) + " " + GetCard(c3);
trace("HAND #" + totalHands + ": " + hand ); 
hands[hand] = totalHands;
}
}
}
}
return hands;
}

public function GetCard(card:Number)
{
var cardString = "";
var rankArray = "23456789TJQKA";
var suitArray = "cdhs";
cardString += rankArray.charAt(card % 13);
cardString += suitArray.charAt(card / 13);
return cardString;
}
Now you can do:

Code:
var hands = EnumerateOmahaHands()
var handNumber = hands["2c 6s 8h Td"];
And it's gonna give you the number !
PLO Hand Lookup program Quote
09-28-2018 , 02:37 PM
Code:
// The first 52 prime numbers.
uint32_t PRIMES[52] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239};

// NOTE: Cards can be passed in any order as each permutation will have the same unique product of primes.
// NOTE: Max key value will be: 227*229*233*239=2,894,777,321 so will fit in an unsigned 32bit integer.
uint32_t getKey(int c0, int c1, int c2, int c3) {
  return PRIMES[c0]*PRIMES[c1]*PRIMES[c2]*PRIMES[c3];
}

// Used to map from key --> hand index.
std::unordered_map<uint32_t,int> hash;

// You can now populate this as you create your ordering:
int handIndex=0;
for (int c0 = 0; c0 < 49; c0++) {	
  for (int c1 = c0+1; c1 < 50; c1++) {
    for (int c2 = c1+1; c2 < 51; c2++) {
      for (int c3 = c2+1; c3 < 52; c3++) {
        hash[getKey(c0,c1,c2,c3)]=handIndex;
        handIndex++;
      }
    }
  }
}

// Now you can lookup your ordered hand index in O(1) from an unordered hand using:
int handIndex=hash.at(getKey(c0,c1,c2,c3));
Juk
PLO Hand Lookup program Quote

      
m