public PairwiseGenerator(int[] counts) { this.counts = counts; dimensions = counts.Length; coveringTables = new CoveringTable[dimensions][]; for (int i = 0; i < dimensions; i++) { coveringTables[i] = new CoveringTable[dimensions]; for (int j = 0; j < i; j++) { int[,] coverings = new int[counts[i], counts[j]]; coveringTables[i][j] = new CoveringTable(coverings, false); coveringTables[j][i] = new CoveringTable(coverings, true); } } tieBreaker = new Random(0); // Note: Uses a constant seed to be deterministic. }
public bool Next(int[] indices) { for (int i = 0; i < dimensions; i++) { indices[i] = -1; } bool foundUncovered = false; for (int firstDimension = 0; firstDimension < dimensions; firstDimension++) { if (indices[firstDimension] >= 0) { continue; } // Find the value in the first dimension that produces the best score over all values in all other dimensions. int firstCount = counts[firstDimension]; int bestFirstScore = 0; int bestFirstIndex = 0; int ties = 0; for (int firstIndex = 0; firstIndex < firstCount; firstIndex++) { int firstScore = 0; for (int secondDimension = 0; secondDimension < dimensions; secondDimension++) { if (firstDimension == secondDimension) { continue; } CoveringTable coverings = coveringTables[firstDimension][secondDimension]; if (firstDimension < secondDimension) { int secondCount = counts[secondDimension]; for (int secondIndex = 0; secondIndex < secondCount; secondIndex++) { if (coverings.GetCoveringCount(firstIndex, secondIndex) == 0) { firstScore += 1; break; } } } else { if (coverings.GetCoveringCount(firstIndex, indices[secondDimension]) == 0) { firstScore += 1; } } } if (firstScore < bestFirstScore) { continue; } if (firstScore == bestFirstScore) { // Randomly choose which of the ties to keep with equal probability. // This helps to reduce the variance between covered pairs. ties += 1; if (tieBreaker.Next(ties) != 0) { continue; } } else { bestFirstScore = firstScore; } bestFirstIndex = firstIndex; } if (bestFirstScore != 0) { // If the best score is non-zero, then we know there exists an uncovered pair that will be // covered by this choice of first index because we always prefer indexes that produce new coverings. foundUncovered = true; indices[firstDimension] = bestFirstIndex; } else { // If the best score is zero, then it makes no difference which index we choose so we // pick one at random. This helps to reduce the variance between covered pairs // as otherwise we'd constantly be choosing the 0th element. indices[firstDimension] = tieBreaker.Next(firstCount); } } // If we did not find any uncovered pairs, then there are none to be found. if (!foundUncovered) { return(false); } // Mark all pairs that were covered. for (int i = 0; i < dimensions; i++) { for (int j = i + 1; j < dimensions; j++) { coveringTables[i][j].IncrementCoveringCount(indices[i], indices[j]); } } return(true); }