public double?GetExpectedNumberOfTurnsToPaintBalls(int numBalls) { if (this._numBallsToProbabilityDictionary.ContainsKey(numBalls)) { return(this._numBallsToProbabilityDictionary[numBalls]); } if (numBalls <= 0) { return(null); } if (numBalls == 1) { return(0); } var initialStateBallDistribution = new Dictionary <int, int> { { 1, numBalls } }; var initialState = new RandomBallPainterState(initialStateBallDistribution, numBalls); return(this._markovChainSolver.GetExpectedValueOfNumTurnsToReachTerminalState(initialState, GetStateTransitions)); }
/// <summary> /// returns a tuple of (state, probability transition to this state) /// </summary> /// <returns></returns> public Dictionary <RandomBallPainterState, double> GetStateTransitions(RandomBallPainterState state) { Dictionary <RandomBallPainterState, double> stateProbabilityTransition = new Dictionary <RandomBallPainterState, double>(); foreach (var key1 in state.BallDistributionForState.Keys) { var probabilityFirstTypeOfBallDrawn = ((double)key1 * (double)state.BallDistributionForState[key1]) / (double)state.NumBalls; foreach (var key2 in state.BallDistributionForState.Keys) { if (key1 == key2) { // if we've drawn a second ball that has the same number of balls of the same color, there is a chance that // we've drawn the same colored ball twice if we're looking at a color with > 1 ball in it var probabilityOfDrawingSameBallTwice = (key1 - 1) / ((double)state.NumBalls - 1); var currentStateClone = new RandomBallPainterState(state.DeepCloneBallDistribution(), state.NumBalls); stateProbabilityTransition[currentStateClone] = (stateProbabilityTransition.ContainsKey(currentStateClone) ? stateProbabilityTransition[currentStateClone] : 0) + probabilityFirstTypeOfBallDrawn * probabilityOfDrawingSameBallTwice; if (state.BallDistributionForState[key2] == 1) { continue; } // it's also possible we drew a ball of a different color that happened to have the same number of balls of the same color // as the first ball that was drawn var probabilityOfDrawingDifferentBall = (double)key2 * ((double)state.BallDistributionForState[key2] - 1) / ((double)state.NumBalls - 1); var nextStateDistribution = state.DeepCloneBallDistribution(); // if this happened, the second ball's group will lose 1 ball, and the first ball's group will gain 1 ball // so we increment the value for key1+1 by 1, increment the value for key1-1 by 1, decrement the value for key1 by 2 nextStateDistribution[key1 + 1] = (nextStateDistribution.ContainsKey(key1 + 1) ? nextStateDistribution[key1 + 1] : 0) + 1; nextStateDistribution[key1] = nextStateDistribution[key1] - 2; // if there are no longer values for this key, remove it if (nextStateDistribution[key1] == 0) { nextStateDistribution.Remove(key1); } if (key1 - 1 > 0) { nextStateDistribution[key1 - 1] = (nextStateDistribution.ContainsKey(key1 - 1) ? nextStateDistribution[key1 - 1] : 0) + 1; } var nextState = new RandomBallPainterState(nextStateDistribution, state.NumBalls); stateProbabilityTransition[nextState] = (stateProbabilityTransition.ContainsKey(nextState) ? stateProbabilityTransition[nextState] : 0) + probabilityFirstTypeOfBallDrawn * probabilityOfDrawingDifferentBall; } // if we drew a second type of ball else { var probabilityDrawingSecond = ((double)key2 * (double)state.BallDistributionForState[key2]) / ((double)state.NumBalls - 1); // in this case, the first ball's and second ball's groups will lose 1 // the first ball's group + 1 will gain 1 and the second ball's group - 1 will gain 1 var nextStateDistribution = state.DeepCloneBallDistribution(); nextStateDistribution[key1] = nextStateDistribution[key1] - 1; if (nextStateDistribution[key1] == 0) { nextStateDistribution.Remove(key1); } nextStateDistribution[key2] = nextStateDistribution[key2] - 1; if (nextStateDistribution[key2] == 0) { nextStateDistribution.Remove(key2); } nextStateDistribution[key1 + 1] = (nextStateDistribution.ContainsKey(key1 + 1) ? nextStateDistribution[key1 + 1] : 0) + 1; if (key2 - 1 > 0) { nextStateDistribution[key2 - 1] = (nextStateDistribution.ContainsKey(key2 - 1) ? nextStateDistribution[key2 - 1] : 0) + 1; } var nextState = new RandomBallPainterState(nextStateDistribution, state.NumBalls); stateProbabilityTransition[nextState] = (stateProbabilityTransition.ContainsKey(nextState) ? stateProbabilityTransition[nextState] : 0) + probabilityFirstTypeOfBallDrawn * probabilityDrawingSecond; } } } return(stateProbabilityTransition); }