-3

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

Steve W
  • 1,108
  • 3
  • 13
  • 35
  • 1
    Please share a [mcve] (i.e. cut down the volume of code). – mjwills Sep 19 '21 at 12:12
  • I marked it in the code with // OUT OF BOUNDS ERROR OCCURS HERE This is about the minimum I can provide. In the program class I've given an example that works fine and an example that throws the error – Steve W Sep 19 '21 at 12:13
  • For `i == 3` your call `permutations = (new Permutation()).Enumerate(players, i + 2);` the first permuation returned is `[0,1,2]` but later you try to access `permutation[i]` (which in your case would be `permutation[3]`) which of course fails ... – derpirscher Sep 19 '21 at 12:31
  • [What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?](https://stackoverflow.com/questions/20940979/what-is-an-indexoutofrangeexception-argumentoutofrangeexception-and-how-do-i-f) – Ondrej Tucny Sep 19 '21 at 12:35
  • the size of an enumeration is the number of element it contains (ie you put in there) And for an array like `int[] xxx = new int[7]` it's the size you define – derpirscher Sep 19 '21 at 12:38
  • I understand that. What I don't understand in this code is how the items are put into it so I can't figure out how to make it the right size. – Steve W Sep 19 '21 at 12:39
  • I'd appreciate some help to fix the issue. I understand what an out of range exception is and why it occurs. But I do not understand how the array size is getting set in this example and would appreciate some help to fix it. – Steve W Sep 19 '21 at 12:57
  • Also, as a general rule, you may want to avoid doing `Count()` calls on enumerables due to double enumeration. Often that is a code smell suggesting the parameter should be of type `IReadOnlyList` instead. – mjwills Sep 19 '21 at 13:47

1 Answers1

2

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.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
  • I really appreciate that Luke. It works perfectly now. I don't know why people were being unhelpful and downvoting my question. That seems to happen a lot lately and I just don't get why. Thanks so much for voting to reopen and resolve my problem which wasn't a basic outofbounds error issue that people seemed to be pushing. – Steve W Sep 19 '21 at 13:40
  • 1
    @SteveW thanks, but I think you could have improved your question by cutting out all of the calculations and just having the `Permutation` class. If you had included only that one class, and your question was something like "why do I get permutations of length 3 when I ask for permutations of length 4?" then you may have had a more favourable response. On SO we favour minimal reproducible examples, and the extra classes in your question mean that the code in your question isn't as minimal as it could have been. – Luke Woodward Sep 19 '21 at 13:48
  • @SteveW We weren't trying to be unhelpful. It is (or at the very least _very much looked like_) a common question, asked 20 times a day - and your lack of [mcve] didn't help things. I know it feels like we are getting in the way - but we are honestly trying to point you at resources to diagnose and solve the issue quickly. – mjwills Sep 19 '21 at 13:48
  • Honestly Luke, I wouldn't have known that the Permutation class alone would be enough. I didn't know where the source of the problem was coming from so I thought all the code was necessary. I guess when people can't solve a problem, its less obvious to them as to what is the minimum needed to get help. – Steve W Sep 19 '21 at 13:52