I have some code that wasn't written by myself and I'm trying to use it to build a Poker ICM calculator program. This program takes an array of stack sizes and prize payouts and calculates each players prize equity. The code works for 3 prizes but when I add a 4th prize I get an index out of bounds error because I'm trying to get permutation[3] when the object only contains indexes 0 to 2. The problem is I can't understand how the size of permuation gets set so I can't figure out how to adjust the code to work for higher numbers of prizes. I'd appreciate some help. Below is a minimum working example with working and non-working code in the main method and a comment to indicate where the error occurs.
ICMCalculator.cs
class ICMCalculator
{
public double[] CalcEV(int[] structure, int[] chips)
{
//the probability of a players position
double[,] probabilitys = new double[structure.Length, chips.Length];
//the expected value of the player
double[] EVs = new double[chips.Length];
int[] players = new int[chips.Length];
for (int i = 0; i < players.Length; ++i)
players[i] = i;
IEnumerable<int[]> permutations;
for (int i = 0; i < structure.Length; ++i)
{
permutations = (new Permutation()).Enumerate(players, i + 2);
foreach (int[] permutation in permutations)
{
// OUT OF BOUNDS ERROR OCCURS HERE
probabilitys[i, permutation[i]] += CalcPermutationProbability(permutation, chips);
}
}
for (int i = 0; i < structure.Length; ++i)
{
for (int j = 0; j < chips.Length; ++j)
EVs[j] += probabilitys[i, j] * structure[i];
}
return EVs;
}
private double CalcPermutationProbability(int[] permutations, int[] chips)
{
double probability = 1.0F;
int chips_sum = chips.Sum();
for (int i = 0; i < permutations.Length; ++i)
{
probability *= System.Convert.ToDouble(chips[permutations[i]]) / System.Convert.ToDouble(chips_sum);
chips_sum -= chips[permutations[i]];
}
return probability;
}
}
Permutation.cs
class Permutation
{
public IEnumerable<T[]> Enumerate<T>(IEnumerable<T> nums, int length)
{
var perms = _GetPermutations<T>(new List<T>(), nums.ToList(), length);
return perms;
}
private IEnumerable<T[]> _GetPermutations<T>(IEnumerable<T> perm, IEnumerable<T> nums, int length)
{
if (length - perm.Count() <= 0)
{
yield return perm.ToArray();
}
else
{
foreach (var n in nums)
{
var result = _GetPermutations<T>(perm.Concat(new T[] { n }),
nums.Where(x => x.Equals(n) == false), length - perm.Count());
foreach (var xs in result)
yield return xs.ToArray();
}
}
}
}
Utils.cs
class Utils
{
public static string DoubleArrayToString(double[] doubles)
{
StringBuilder sb = new StringBuilder();
foreach (double dub in doubles)
{
sb.AppendLine(Utils.Format2DP(dub));
}
return sb.ToString().Trim();
}
}
Program.cs
static class Program
{
static void Main()
{
// THIS WORKS
ICMCalculator ev = new ICMCalculator();
int[] stacks = new int[] { 4500, 2700, 1800, 1000, 500 };
int[] prizes = new int[] { 84,36,18 };
int prizePool = prizes.Sum();
double[] equity = ev.CalcEV(prizes, stacks);
Console.WriteLine(Utils.DoubleArrayToString(equity));
// THIS THROWS INDEX ERROR
ev = new ICMCalculator();
stacks = new int[] { 4500, 2700, 1800, 1000, 500 };
prizes = new int[] { 84,36,18,9 };
prizePool = prizes.Sum();
equity = ev.CalcEV(prizes, stacks);
Console.WriteLine(Utils.DoubleArrayToString(equity));
}
}
To be clear, I believe that each 'permutation' should be the length of structure.Length. I don't want to avoid the out of bounds error by reducing the max value of i. I think I need to increase the size of each permuation to match structure.Length but I'm unable to figure out how to do this.
If resolved correctly, the code that currently fails should output the values: 50.82, 37.85, 29.16, 19.01, 10.15
I think the problem is in this line:
var result = _GetPermutations<T>(perm.Concat(new T[] { n }),
nums.Where(x => x.Equals(n) == false), length - perm.Count());
Instead, I think it should be
var result = _GetPermutations<T>(perm.Concat(new T[] { n }),
nums.Where(x => x.Equals(n) == false), length);
The problem here is that we are double-counting the length of the permutation found so far when we determine whether to stop further recursion.
The condition length - perm.Count() <= 0
(which can be simplified to perm.Count() >= length
) is used to stop further calls to _GetPermutations
if the permutation perm
generated so far is long enough to be returned. This relies on the parameter length
being the required length of the resulting permutations. However, by passing length - perm.Count()
in the recursive call to _GetPermutations()
, then we are reducing the value of length
that inner calls to _GetPermutations()
receive, causing them to stop the recursion early with permutations that are too short.