All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rwth.swc.coffee4j.engine.util.Combinator Maven / Gradle / Ivy

package de.rwth.swc.coffee4j.engine.util;

import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;

import static de.rwth.swc.coffee4j.engine.util.CombinationUtil.NO_VALUE;
import static de.rwth.swc.coffee4j.engine.util.CombinationUtil.emptyCombination;

/**
 * Utility methods used for combinatorial tasks in the context of combinatorial
 * test generation.
 * 

* Uses the indexing schema introduced in * {@link CombinationUtil}. */ public final class Combinator { private static final String PARAMETERS_NOT_NULL = "Parameters cannot be null"; private static final String AT_LEAST_ONE_PARAMETER = "At least one parameter has to be given"; private static final String TOO_MANY_PARAMETERS = "The combination size cannot be smaller than the number" + "of parameters"; private static final String TOO_HIGH_PARAMETERS = "The combination size cannot be smaller than the highest" + "parameter number"; private static final String SIZE_NOT_NEGATIVE = "The size of combinations cannot be negative"; private Combinator() { } /** * Computes the full cartesian product of the given parameters. * Each entry form the cartesian product is stored in an array of size * combinationSize so that the combinations can be used for larger test * inputs in later iterations of the IPOG algorithm. * * @param parameters the parameters for whose values the cartesian product * shall be computed. Must no be {@code null} or empty * @param combinationSize the size of the combinations returned. Empty * places are filled with * {@link CombinationUtil#NO_VALUE}. * Must not be smaller than the number of parameters * @return all tuples of the cartesian product * @throws NullPointerException if parameters is {@code null} * @throws IllegalArgumentException if there are no parameters or if the * combinationsSize is too small */ public static List computeCartesianProduct(Int2IntMap parameters, int combinationSize) { Preconditions.notNull(parameters, PARAMETERS_NOT_NULL); Preconditions.check(!parameters.isEmpty(), AT_LEAST_ONE_PARAMETER); Preconditions.check(combinationSize >= parameters.size(), TOO_MANY_PARAMETERS); Preconditions.check(combinationSize > parameters.keySet().stream() .mapToInt(parameter -> parameter).max().orElse(0), TOO_HIGH_PARAMETERS); List combinations = new ArrayList<>(); int[] currentIndex = new int[parameters.size()]; int[] keys = parameters.keySet().toIntArray(); Arrays.sort(keys); do { int[] currentCombination = new int[combinationSize]; Arrays.fill(currentCombination, CombinationUtil.NO_VALUE); for (int i = 0; i < keys.length; i++) { int index = keys[i]; int value = currentIndex[i]; currentCombination[index] = value; } combinations.add(currentCombination); } while (tryIncreaseByOne(currentIndex, keys, parameters)); return combinations; } private static boolean tryIncreaseByOne(int[] currentIndex, int[] keys, Int2IntMap parameters) { for (int i = 0; i < currentIndex.length; i++) { currentIndex[i]++; if (currentIndex[i] < parameters.get(keys[i])) { return true; } else { currentIndex[i] = 0; } } return false; } /** * Computes all subsets of parameter indices with the given size. * For example if the parameters 1, 2, 3, and 4 are given and the * specified size is 2, the parameters subsets (1, 2), (1, 3), * (1, 4), (2, 3), (2, 4), (3, 4) are returned. * * @param parameters the set of parameters for which all subsets shall be * generated. Must not be {@code null} * @param size the size of the returned subsets. Must not be negative or * greater than the number of parameters * @return all subsets of parameters of the given size * @throws NullPointerException if parameters is {@code null} * @throws IllegalArgumentException if the size is negative or too large */ public static List computeParameterCombinations(int[] parameters, int size) { Preconditions.notNull(parameters, PARAMETERS_NOT_NULL); Preconditions.check(size >= 0, SIZE_NOT_NEGATIVE); return computeParameterCombinationsRecursively(parameters, size); } private static List computeParameterCombinationsRecursively(int[] parameters, int k) { if (k == 0 || parameters.length == 0 || parameters.length < k) { return Collections.emptyList(); } else if (k == 1) { List combinations = new ArrayList<>(parameters.length); for (int parameter : parameters) { IntSet set = new IntOpenHashSet(1); set.add(parameter); combinations.add(set); } return combinations; } else if (parameters.length == k) { List combinations = new ArrayList<>(1); combinations.add(new IntOpenHashSet(parameters)); return combinations; } else { int[] tail = Arrays.copyOfRange(parameters, 1, parameters.length); List tailSubsets = computeParameterCombinationsRecursively(tail, k - 1); for (IntSet set : tailSubsets) { set.add(parameters[0]); } List subsets = computeParameterCombinationsRecursively(tail, k); List combinations = new ArrayList<>(tailSubsets.size() + subsets.size()); combinations.addAll(tailSubsets); combinations.addAll(subsets); return combinations; } } /** * Computes subsets of parameter indices with the given size multiplied with negative parameters *

* For example, * f( [0, 1, 2, 3], [ 0 ], 0) == { (0) } * f( [0, 1, 2, 3], [ 0 ], 1) == { (0, 1), (0, 2), (0, 3) } * f( [0, 1, 2, 3], [ 0 ], 2) == { (0, 1, 2), (0, 1, 3), (0, 2, 3) } * f( [0, 1, 2, 3], [ 0 ], 3) == { (0, 1, 2, 3) } * f( [0, 1, 2, 3], [ 0 ], 4) == { (0, 1, 2, 3) } * f( [0, 1, 2, 3], [ 0 ], 5) == { (0, 1, 2, 3) } * * @param parameters the set of parameters for which all subsets shall be * generated. Must not be {@code null} * @param negativeParameters the subset of parameters which are always present * @param size the size of the returned subsets. Must not be negative or * greater than the number of parameters * @return all subsets of parameters of the given size * @throws NullPointerException if parameters is {@code null} * @throws IllegalArgumentException if the size is negative or too large */ public static List computeNegativeParameterCombinations(int[] parameters, int[] negativeParameters, int size) { Preconditions.notNull(parameters, PARAMETERS_NOT_NULL); Preconditions.check(size >= 0, SIZE_NOT_NEGATIVE); Preconditions.check(Arrays.stream(negativeParameters).allMatch(n -> ArrayUtil.contains(parameters, n))); return computeNegativeParameterCombinationsRecursively(parameters, negativeParameters, size); } private static List computeNegativeParameterCombinationsRecursively(int[] parameters, int[] negativeParameters, int k) { if (k == 0) { List combinations = new ArrayList<>(1); combinations.add(new IntOpenHashSet(negativeParameters)); return combinations; } else { final int[] nonNegativeParameters = ArrayUtil.exclude(parameters, negativeParameters); final int combinationsSize = Math.min(nonNegativeParameters.length, k); List subCombinations = computeParameterCombinationsRecursively(nonNegativeParameters, combinationsSize); if(subCombinations.isEmpty()) { final IntSet set = new IntOpenHashSet(negativeParameters.length); for (int negativeParameter : negativeParameters) { set.add(negativeParameter); } return Collections.singletonList(set); } else { for (IntSet set : subCombinations) { for (int negativeParameter : negativeParameters) { set.add(negativeParameter); } } } return subCombinations; } } /** * Computes all size-value-combinations there are with the given parameters. * For example, for the given parameters [2, 2, 2] (three parameters with 2 values each) and size 2, the * following combinations are returned: * [0, 0, -1] * [0, 1, -1] * [1, 0, -1] * [1, 1, -1] * [0, -1, 0] * [0, -1, 1] * [1, -1, 0] * [1, -1, 1] * [-1, 0, 0] * [-1, 0, 1] * [-1, 1, 0] * [-1, 1, 1] * * @param parameters all parameters. They are defined as their number of values. So [2, 3] means the first parameter * has two values, and the second one has three. Must not be {@code null} * @param size the size of sub-combinations of values in the parameters that are calculated * @return all sub-combinations of the values with the given size as demonstrated above. The order of combinations * is not defined and may change in subsequent implementations. In any combinations the values for parameters * are ordered the same way as the parameters supplied to the method */ public static Set computeCombinations(int[] parameters, int size) { Preconditions.notNull(parameters); Preconditions.check(size >= 0); final List parameterCombinations = computeParameterCombinationsRecursively(IntStream.range(0, parameters.length).toArray(), size); final Set combinations = new HashSet<>(); for (IntSet parameterCombination : parameterCombinations) { final Int2IntMap parameterSizes = new Int2IntOpenHashMap(parameterCombination.size()); for (int parameter : parameterCombination) { parameterSizes.put(parameter, parameters[parameter]); } combinations.addAll(computeCartesianProduct(parameterSizes, parameters.length)); } return combinations; } /** * Computes all sub-combinations with the given size that the combination has. The combinations is allowed to have * values not set. For example, [-1, 2, 3, 1, -1, 3] called with 2 would return * [-1, 2, 3, -1, -1, -1] * [-1, 2, -1, 1, -1, -1] * [-1, 2, -1, -1, -1, 3] * [-1, -1, 3, 1, -1, -1] * [-1, -1, 3, -1, -1, 3] * [-1, -1, -1, 1, -1, 3] * * @param combination a combination. Must not be {@code null} * @param size the size of sub-combinations. Must be positive * @return all sub-combinations with the given size of the combinations. No order is guaranteed. The parameters * are in the same order as with the given combination */ public static List computeSubCombinations(int[] combination, int size) { Preconditions.notNull(combination); Preconditions.check(size >= 0); return computeSubCombinationsRecursively(combination, emptyCombination(combination.length), 0, size); } private static List computeSubCombinationsRecursively(int[] combination, int[] currentSubCombination, int index, int size) { final int currentSize = CombinationUtil.numberOfSetParameters(currentSubCombination); if (currentSize == size) { return Collections.singletonList(Arrays.copyOf(currentSubCombination, currentSubCombination.length)); } if (index == combination.length || size > combination.length - index + 1 + currentSize) { return Collections.emptyList(); } final List result = new ArrayList<>(computeSubCombinationsRecursively(combination, currentSubCombination, index + 1, size)); if (combination[index] != NO_VALUE) { currentSubCombination[index] = combination[index]; result.addAll(computeSubCombinationsRecursively(combination, currentSubCombination, index + 1, size)); currentSubCombination[index] = NO_VALUE; } return result; } /** * Computes all sub-combinations of this combination. This returns the same result as calling * {@link #computeCombinations(int[], int)} with sizes from 1 to combinations.length. * * @param combination a combination. Must not be {@code null} but can have unset values * @return all sub-combinations (including the combination itself) */ public static List computeSubCombinations(int[] combination) { Preconditions.notNull(combination); return computeSubCombinationsRecursively(combination, emptyCombination(combination.length), 0); } private static List computeSubCombinationsRecursively(int[] combination, int[] currentSubCombination, int index) { if (index == combination.length) { if (CombinationUtil.numberOfSetParameters(currentSubCombination) > 0) { return Collections.singletonList(Arrays.copyOf(currentSubCombination, currentSubCombination.length)); } else { return Collections.emptyList(); } } final List result = new ArrayList<>(computeSubCombinationsRecursively(combination, currentSubCombination, index + 1)); if (combination[index] != NO_VALUE) { currentSubCombination[index] = combination[index]; result.addAll(computeSubCombinationsRecursively(combination, currentSubCombination, index + 1)); currentSubCombination[index] = NO_VALUE; } return result; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy