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

io.specmatic.core.pattern.CombinationSpec.kt Maven / Gradle / Ivy

Go to download

Turn your contracts into executable specifications. Contract Driven Development - Collaboratively Design & Independently Deploy MicroServices & MicroFrontends.

There is a newer version: 2.0.37
Show newest version
package io.specmatic.core.pattern

/**
 * Provides utility access to all combinations of multiple sets of candidate values. Represented as the cartesian
 * product of all sets where each combination is represented as a numerical index from 0 to (MAX_COMBOS - 1).
 *
 * Supports sequential iteration over all combinations, which is used to test as many combinations as possible.
 *
 * Supports translating a combination of candidate values to an index, which is used to first test priority combinations
 * (and to remember these when later iterating remaining combinations to not double-include these priority combinations)
 *
 * See  this
 * stackoverflow article accepted answer. A notable difference is that our representation is reversed such that
 * a sequential iteration produces all combinations with the first candidate value of the first set before producing
 * all combinations using the second candidate value of the first set, and so on for each subsequent value and set.
 *
 * Note: This code now returns a sequence instead of a list, and no longer calculates up-front the max size of any
 * sequence. There are significant changes to how it works. However, it continues to prioritise combinations in the
 * same way as before.
 */
class CombinationSpec(
    keyToCandidatesOrig: Map>>,
    private val maxCombinations: Int
) {
    companion object {
        fun  from(keyToCandidates: Map>, maxCombinations: Int): CombinationSpec {
            return CombinationSpec(keyToCandidates.mapValues { it.value.map { HasValue(it) } }, maxCombinations)
        }
    }

    val keyToCandidates: Map>> = keyToCandidatesOrig

    init {
        if (maxCombinations < 1) throw IllegalArgumentException("maxCombinations must be > 0 and <= ${Int.MAX_VALUE}")
    }

    val selectedCombinations: Sequence>> = toSelectedCombinations(keyToCandidates, maxCombinations)

    fun  toSelectedCombinations(rawPatternCollection: Map>>, maxCombinations: Int): Sequence>> {
        val patternCollection = rawPatternCollection.filterValues { it.any() }

        if (patternCollection.isEmpty())
            return emptySequence()

        val cachedValues = patternCollection.mapValues { mutableListOf>() }
        val prioritisedGenerations = mutableSetOf>>()

        val ranOut = cachedValues.mapValues { false }.toMutableMap()

        val iterators = patternCollection.mapValues {
            it.value.iterator()
        }.filter {
            it.value.hasNext()
        }

        return sequence {
            var ctr = 0

            while (true) {
                val nextValue: Map> = iterators.mapValues { (key, iterator) ->
                    val nextValueFromIterator = if (iterator.hasNext()) {
                        val value = iterator.next()

                        cachedValues.getValue(key).add(value)

                        value
                    } else {
                        ranOut[key] = true

                        val cachedValuesForKey = cachedValues.getValue(key)
                        val value = cachedValuesForKey.get(ctr % cachedValuesForKey.size)

                        value
                    }

                    nextValueFromIterator
                }

                val _nextValue: ReturnValue> = nextValue.mapFold()

                if(ranOut.all { it.value })
                    break

                ctr ++

                yield(_nextValue)
                prioritisedGenerations.add(_nextValue)

                if(prioritisedGenerations.size == maxCombinations)
                    break
            }

            if(prioritisedGenerations.size == maxCombinations)
                return@sequence

            val otherPatterns: Sequence>> = allCombinations(patternCollection).map { it.mapFold() }

            val maxCountOfUnPrioritisedGenerations = maxCombinations - prioritisedGenerations.size

            // TODO Handle equality between return values to ignore the messages and breadcrumbs
            val filtered = otherPatterns.filter { it !in prioritisedGenerations }
            val limited = filtered.take(maxCountOfUnPrioritisedGenerations)

            yieldAll(limited)
        }
    }

    fun  allCombinations(patternCollection: Map>): Sequence> {
        if(patternCollection.isEmpty())
            return sequenceOf(emptyMap())

        val entry = patternCollection.entries.first()

        val subsequentGenerations: Sequence> = allCombinations(patternCollection - entry.key)

        return entry.value.flatMap { value ->
            subsequentGenerations.map { mapOf(entry.key to value) + it }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy