/// <summary> /// allows us to have zero-probability values /// </summary> private static int ExemptZeroes(Object[] objs, IRandomChoiceChooserD chooser, int index) { //System.out.PrintLn(index); if (chooser.GetProbability(objs[index]) == 0.0) // I need to scan forward because I'm in a left-trail // scan forward { while (index < objs.Length - 1 && chooser.GetProbability(objs[index]) == 0.0) { index++; } } // scan backwards else { while (index > 0 && chooser.GetProbability(objs[index]) == chooser.GetProbability(objs[index - 1])) { index--; } } return(index); }
/// <summary> /// Picks a random item from an array of objects, each with an /// associated probability that is accessed by taking an object /// and passing it to chooser.GetProbability(obj). The objects' /// probabilities are normalized and summed as follows: /// For example, if four probabilities are {0.3, 0.2, 0.1, 0.4}, /// then they should get normalized and summed by the outside owners /// as: {0.3, 0.5, 0.6, 1.0}. If probabilities.Length < checkboundary, /// then a linear search is used, else a binary search is used. /// </summary> public static int PickFromDistribution(Object[] objs, IRandomChoiceChooserD chooser, double prob, int checkboundary) { if (prob < 0.0 || prob > 1.0) { throw new ArithmeticException("Invalid probability for pickFromDistribution (must be 0.0<=x<=1.0)"); } if (objs.Length == 1) { return(0); } if (objs.Length < checkboundary) { // simple linear scan for (var x = 0; x < objs.Length - 1; x++) { if (chooser.GetProbability(objs[x]) > prob) { return(ExemptZeroes(objs, chooser, x)); } } return(ExemptZeroes(objs, chooser, objs.Length - 1)); } // binary search var top = objs.Length - 1; var bottom = 0; while (top != bottom) { var cur = (top + bottom) / 2; // integer division if (chooser.GetProbability(objs[cur]) > prob) { if (cur == 0 || chooser.GetProbability(objs[cur - 1]) <= prob) { return(ExemptZeroes(objs, chooser, cur)); } // step down else { top = cur; } } else if (cur == objs.Length - 1) { // oops return(ExemptZeroes(objs, chooser, cur)); } else if (bottom == cur) { // step up bottom++; } // (8 + 9)/2 = 8 else { bottom = cur; // (8 + 10) / 2 = 9 } } return(ExemptZeroes(objs, chooser, bottom)); // oops }
/// <summary> /// Picks a random item from an array of objects, each with an /// associated probability that is accessed by taking an object /// and passing it to chooser.getProbability(obj). The objects' /// probabilities are normalized and summed as follows: /// For example, if four probabilities are {0.3, 0.2, 0.1, 0.4}, /// then they should get normalized and summed by the outside owners /// as: {0.3, 0.5, 0.6, 1.0}. If probabilities.Length < CHECKBOUNDARY, /// then a linear search is used, else a binary search is used. /// </summary> public static int PickFromDistribution(Object[] objs, IRandomChoiceChooserD chooser, double prob) { return(PickFromDistribution(objs, chooser, prob, CHECKBOUNDARY)); }
/// <summary> /// Normalizes the probabilities associated with an array of objects, /// then converts them into continuing sums. /// This prepares them for being usable in pickFromDistribution. /// If the probabilities are all 0, then selection is uniform, unless allowAllZeros /// is false, in which case an ArithmeticException is thrown. If any of them are negative, /// or if the distribution is empty, then an ArithmeticException is thrown. /// For example, /// {0.6, 0.4, 0.2, 0.8} -> {0.3, 0.2, 0.1, 0.4} -> {0.3, 0.5, 0.6, 1.0} /// The probabilities are retrieved and set using chooser. /// </summary> public static void OrganizeDistribution(Object[] objs, IRandomChoiceChooserD chooser, bool allowAllZeros) { // first normalize var sum = 0.0; if (objs.Length == 0) { throw new ArithmeticException("Distribution has no elements"); } foreach (var t in objs) { if (chooser.GetProbability(t) < 0.0) { throw new ArithmeticException("Distribution has negative probabilities"); } sum += chooser.GetProbability(t); } if (sum == 0.0) { if (!allowAllZeros) { throw new ArithmeticException("Distribution has all zero probabilities"); } else { foreach (var t in objs) { chooser.SetProbability(t, 1.0); } sum = objs.Length; } } foreach (var t in objs) { chooser.SetProbability(t, chooser.GetProbability(t) / sum); } // now sum sum = 0.0; foreach (var t in objs) { sum += chooser.GetProbability(t); chooser.SetProbability(t, sum); } // now we need to work backwards setting 0 values int x2; for (x2 = objs.Length - 1; x2 > 0; x2--) { if (chooser.GetProbability(objs[x2]) == chooser.GetProbability(objs[x2 - 1])) { // we're 0.0 chooser.SetProbability(objs[x2], 1.0); } else { break; } } chooser.SetProbability(objs[x2], 1.0); }
/// <summary> /// Same as organizeDistribution(objs, chooser, <b>false</b>); /// </summary> public static void OrganizeDistribution(Object[] objs, IRandomChoiceChooserD chooser) { OrganizeDistribution(objs, chooser, false); }