iRomul
iRomul

Reputation: 351

Create iterable combinations of another iterable objects

I need to create all combinations of set of iterable objects. I have some code on Groovy for generating combinations:

private static List<Map> expandLensesPositions(Map entry) {
    def expandedPositions = []

    def entryCopy = entry.clone() as Map

    def sphFrom = entryCopy.remove("sphFrom") as BigDecimal
    def sphTo = entryCopy.remove("sphTo") as BigDecimal

    def cylFrom = entryCopy.remove("cylFrom") as BigDecimal
    def cylTo = entryCopy.remove("cylTo") as BigDecimal

    def addFrom = entryCopy.remove("addFrom") as BigDecimal
    def addTo = entryCopy.remove("addTo") as BigDecimal

    def diameter = entryCopy.remove("diameterFrom")

    for (def sph in generateSeries(sphFrom, sphTo)) {
        for (def cyl in generateSeries(cylFrom, cylTo)) {
            for (def add in generateSeries(addFrom, addTo)) {
                def expandedEntryProps = [dioptre:sph, diameter:diameter, cylinderDioptre:cyl, addidation:add]

                expandedPositions << entryCopy + expandedEntryProps
            }
        }
    }

    return expandedPositions
}

In this code generateSeries(from, to) returns Iterable<BigDecimal> object. In this implementation, the results are stored in ArrayList expandedPositions, but this approach is unacceptable with a large number of resulting elements. Is there a way to create wrapper for all iterable objects and iterate over all combinations?

Upvotes: 1

Views: 333

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42224

In Groovy you can use GroovyCollections.combinations(Iterable collection) function to create a list of all combinations, e.g.

[[0,1], [2,3], [4,5]].combinations()

creates following list of combinations:

[[0, 2, 4], [1, 2, 4], [0, 3, 4], [1, 3, 4], [0, 2, 5], [1, 2, 5], [0, 3, 5], [1, 3, 5]]

Consider following example that creates a list of map entries:

def sph = [1,2,3,4,5]
def cyl = [6,7,8,9,10]
def add = [11,12,13,14,15]
def diameter = 0.0

def result = [sph, cyl, add].combinations().collect { row ->
    [dioptre: row[0], diameter: diameter, cylinderDioptre: row[1], addidation: row[2]]
}

result.each {
    println it
}

The output it creates is following (125 combinations):

[dioptre:1, diameter:0.0, cylinderDioptre:6, addidation:11]
[dioptre:2, diameter:0.0, cylinderDioptre:6, addidation:11]
[dioptre:3, diameter:0.0, cylinderDioptre:6, addidation:11]
[dioptre:4, diameter:0.0, cylinderDioptre:6, addidation:11]
[dioptre:5, diameter:0.0, cylinderDioptre:6, addidation:11]
[dioptre:1, diameter:0.0, cylinderDioptre:7, addidation:11]
[dioptre:2, diameter:0.0, cylinderDioptre:7, addidation:11]
[dioptre:3, diameter:0.0, cylinderDioptre:7, addidation:11]
[dioptre:4, diameter:0.0, cylinderDioptre:7, addidation:11]
[dioptre:5, diameter:0.0, cylinderDioptre:7, addidation:11]
[dioptre:1, diameter:0.0, cylinderDioptre:8, addidation:11]
[dioptre:2, diameter:0.0, cylinderDioptre:8, addidation:11]
[dioptre:3, diameter:0.0, cylinderDioptre:8, addidation:11]
[dioptre:4, diameter:0.0, cylinderDioptre:8, addidation:11]
[dioptre:5, diameter:0.0, cylinderDioptre:8, addidation:11]
[dioptre:1, diameter:0.0, cylinderDioptre:9, addidation:11]
[dioptre:2, diameter:0.0, cylinderDioptre:9, addidation:11]
[dioptre:3, diameter:0.0, cylinderDioptre:9, addidation:11]
[dioptre:4, diameter:0.0, cylinderDioptre:9, addidation:11]
[dioptre:5, diameter:0.0, cylinderDioptre:9, addidation:11]
[dioptre:1, diameter:0.0, cylinderDioptre:10, addidation:11]
[dioptre:2, diameter:0.0, cylinderDioptre:10, addidation:11]
[dioptre:3, diameter:0.0, cylinderDioptre:10, addidation:11]
[dioptre:4, diameter:0.0, cylinderDioptre:10, addidation:11]
[dioptre:5, diameter:0.0, cylinderDioptre:10, addidation:11]
[dioptre:1, diameter:0.0, cylinderDioptre:6, addidation:12]
[dioptre:2, diameter:0.0, cylinderDioptre:6, addidation:12]
[dioptre:3, diameter:0.0, cylinderDioptre:6, addidation:12]
[dioptre:4, diameter:0.0, cylinderDioptre:6, addidation:12]
[dioptre:5, diameter:0.0, cylinderDioptre:6, addidation:12]
[dioptre:1, diameter:0.0, cylinderDioptre:7, addidation:12]
[dioptre:2, diameter:0.0, cylinderDioptre:7, addidation:12]
[dioptre:3, diameter:0.0, cylinderDioptre:7, addidation:12]
[dioptre:4, diameter:0.0, cylinderDioptre:7, addidation:12]
[dioptre:5, diameter:0.0, cylinderDioptre:7, addidation:12]
[dioptre:1, diameter:0.0, cylinderDioptre:8, addidation:12]
[dioptre:2, diameter:0.0, cylinderDioptre:8, addidation:12]
[dioptre:3, diameter:0.0, cylinderDioptre:8, addidation:12]
[dioptre:4, diameter:0.0, cylinderDioptre:8, addidation:12]
[dioptre:5, diameter:0.0, cylinderDioptre:8, addidation:12]
[dioptre:1, diameter:0.0, cylinderDioptre:9, addidation:12]
[dioptre:2, diameter:0.0, cylinderDioptre:9, addidation:12]
[dioptre:3, diameter:0.0, cylinderDioptre:9, addidation:12]
[dioptre:4, diameter:0.0, cylinderDioptre:9, addidation:12]
[dioptre:5, diameter:0.0, cylinderDioptre:9, addidation:12]
[dioptre:1, diameter:0.0, cylinderDioptre:10, addidation:12]
[dioptre:2, diameter:0.0, cylinderDioptre:10, addidation:12]
[dioptre:3, diameter:0.0, cylinderDioptre:10, addidation:12]
[dioptre:4, diameter:0.0, cylinderDioptre:10, addidation:12]
[dioptre:5, diameter:0.0, cylinderDioptre:10, addidation:12]
[dioptre:1, diameter:0.0, cylinderDioptre:6, addidation:13]
[dioptre:2, diameter:0.0, cylinderDioptre:6, addidation:13]
[dioptre:3, diameter:0.0, cylinderDioptre:6, addidation:13]
[dioptre:4, diameter:0.0, cylinderDioptre:6, addidation:13]
[dioptre:5, diameter:0.0, cylinderDioptre:6, addidation:13]
[dioptre:1, diameter:0.0, cylinderDioptre:7, addidation:13]
[dioptre:2, diameter:0.0, cylinderDioptre:7, addidation:13]
[dioptre:3, diameter:0.0, cylinderDioptre:7, addidation:13]
[dioptre:4, diameter:0.0, cylinderDioptre:7, addidation:13]
[dioptre:5, diameter:0.0, cylinderDioptre:7, addidation:13]
[dioptre:1, diameter:0.0, cylinderDioptre:8, addidation:13]
[dioptre:2, diameter:0.0, cylinderDioptre:8, addidation:13]
[dioptre:3, diameter:0.0, cylinderDioptre:8, addidation:13]
[dioptre:4, diameter:0.0, cylinderDioptre:8, addidation:13]
[dioptre:5, diameter:0.0, cylinderDioptre:8, addidation:13]
[dioptre:1, diameter:0.0, cylinderDioptre:9, addidation:13]
[dioptre:2, diameter:0.0, cylinderDioptre:9, addidation:13]
[dioptre:3, diameter:0.0, cylinderDioptre:9, addidation:13]
[dioptre:4, diameter:0.0, cylinderDioptre:9, addidation:13]
[dioptre:5, diameter:0.0, cylinderDioptre:9, addidation:13]
[dioptre:1, diameter:0.0, cylinderDioptre:10, addidation:13]
[dioptre:2, diameter:0.0, cylinderDioptre:10, addidation:13]
[dioptre:3, diameter:0.0, cylinderDioptre:10, addidation:13]
[dioptre:4, diameter:0.0, cylinderDioptre:10, addidation:13]
[dioptre:5, diameter:0.0, cylinderDioptre:10, addidation:13]
[dioptre:1, diameter:0.0, cylinderDioptre:6, addidation:14]
[dioptre:2, diameter:0.0, cylinderDioptre:6, addidation:14]
[dioptre:3, diameter:0.0, cylinderDioptre:6, addidation:14]
[dioptre:4, diameter:0.0, cylinderDioptre:6, addidation:14]
[dioptre:5, diameter:0.0, cylinderDioptre:6, addidation:14]
[dioptre:1, diameter:0.0, cylinderDioptre:7, addidation:14]
[dioptre:2, diameter:0.0, cylinderDioptre:7, addidation:14]
[dioptre:3, diameter:0.0, cylinderDioptre:7, addidation:14]
[dioptre:4, diameter:0.0, cylinderDioptre:7, addidation:14]
[dioptre:5, diameter:0.0, cylinderDioptre:7, addidation:14]
[dioptre:1, diameter:0.0, cylinderDioptre:8, addidation:14]
[dioptre:2, diameter:0.0, cylinderDioptre:8, addidation:14]
[dioptre:3, diameter:0.0, cylinderDioptre:8, addidation:14]
[dioptre:4, diameter:0.0, cylinderDioptre:8, addidation:14]
[dioptre:5, diameter:0.0, cylinderDioptre:8, addidation:14]
[dioptre:1, diameter:0.0, cylinderDioptre:9, addidation:14]
[dioptre:2, diameter:0.0, cylinderDioptre:9, addidation:14]
[dioptre:3, diameter:0.0, cylinderDioptre:9, addidation:14]
[dioptre:4, diameter:0.0, cylinderDioptre:9, addidation:14]
[dioptre:5, diameter:0.0, cylinderDioptre:9, addidation:14]
[dioptre:1, diameter:0.0, cylinderDioptre:10, addidation:14]
[dioptre:2, diameter:0.0, cylinderDioptre:10, addidation:14]
[dioptre:3, diameter:0.0, cylinderDioptre:10, addidation:14]
[dioptre:4, diameter:0.0, cylinderDioptre:10, addidation:14]
[dioptre:5, diameter:0.0, cylinderDioptre:10, addidation:14]
[dioptre:1, diameter:0.0, cylinderDioptre:6, addidation:15]
[dioptre:2, diameter:0.0, cylinderDioptre:6, addidation:15]
[dioptre:3, diameter:0.0, cylinderDioptre:6, addidation:15]
[dioptre:4, diameter:0.0, cylinderDioptre:6, addidation:15]
[dioptre:5, diameter:0.0, cylinderDioptre:6, addidation:15]
[dioptre:1, diameter:0.0, cylinderDioptre:7, addidation:15]
[dioptre:2, diameter:0.0, cylinderDioptre:7, addidation:15]
[dioptre:3, diameter:0.0, cylinderDioptre:7, addidation:15]
[dioptre:4, diameter:0.0, cylinderDioptre:7, addidation:15]
[dioptre:5, diameter:0.0, cylinderDioptre:7, addidation:15]
[dioptre:1, diameter:0.0, cylinderDioptre:8, addidation:15]
[dioptre:2, diameter:0.0, cylinderDioptre:8, addidation:15]
[dioptre:3, diameter:0.0, cylinderDioptre:8, addidation:15]
[dioptre:4, diameter:0.0, cylinderDioptre:8, addidation:15]
[dioptre:5, diameter:0.0, cylinderDioptre:8, addidation:15]
[dioptre:1, diameter:0.0, cylinderDioptre:9, addidation:15]
[dioptre:2, diameter:0.0, cylinderDioptre:9, addidation:15]
[dioptre:3, diameter:0.0, cylinderDioptre:9, addidation:15]
[dioptre:4, diameter:0.0, cylinderDioptre:9, addidation:15]
[dioptre:5, diameter:0.0, cylinderDioptre:9, addidation:15]
[dioptre:1, diameter:0.0, cylinderDioptre:10, addidation:15]
[dioptre:2, diameter:0.0, cylinderDioptre:10, addidation:15]
[dioptre:3, diameter:0.0, cylinderDioptre:10, addidation:15]
[dioptre:4, diameter:0.0, cylinderDioptre:10, addidation:15]
[dioptre:5, diameter:0.0, cylinderDioptre:10, addidation:15]

I guess this final list is not exactly the one that you expect since in your example you push elements to the final list as

 expandedPositions << entryCopy + expandedEntryProps

but doing something like this:

def result = [sph, cyl, add].combinations().collect { row ->
    entryCopy + [dioptre: row[0], diameter: diameter, cylinderDioptre: row[1], addidation: row[2]]
}

should do the trick.

Creating combinations lazily

As you mentioned in the comment below, GroovyCollections.combinations(iterable) creates a list and stores all combinations in it. If you expect creating combinations in more lazy manner, you can consider using custom Iterator that calculates combinations for a specific iteration step. Consider following example:

final class LazyCombinationsIterator<T> implements Iterator<List<T>> {
    final AtomicInteger idx = new AtomicInteger(0)
    final List<List<T>> lists = []
    final int size
    final List<Integer> listCycle

    LazyCombinationsIterator(List<T>... lists) {
        this.lists.addAll(lists)

        this.size = (int) lists.inject(1) { acc, list -> acc * list.size() }

        this.listCycle = [1] + (lists.inject([]) { List<Integer> acc, list ->
            acc << (acc.isEmpty() ? list.size() : acc.last() * list.size())
        } as List)
    }

    @Override
    boolean hasNext() {
        return idx.get() < size
    }

    @Override
    List<T> next() {
        final int i = idx.getAndIncrement()
        final int to = lists.size()

        return (0..<(to)).collect {
            final List<T> list = lists.get(it)
            final int listIdx = (int) (i / listCycle.get(it))
            return list.get(listIdx % list.size())
        }
    }
}

This custom iterator expects at least two lists of type T and generates calculations while iterating over it. It creates the same output as GroovyCollections.combinations(iterable) function, but it does it lazily.

Example 1:

new LazyCombinationsIterator<>([1, 2], [3, 4, 5], [6, 9]).each {
    println it
}

It creates following output:

[1, 3, 6]
[2, 3, 6]
[1, 4, 6]
[2, 4, 6]
[1, 5, 6]
[2, 5, 6]
[1, 3, 9]
[2, 3, 9]
[1, 4, 9]
[2, 4, 9]
[1, 5, 9]
[2, 5, 9]

Example 2:

new LazyCombinationsIterator<>(['a', 'b'], ['c'], ['d', 'e']).each {
    println it
}

It creates following output:

[a, c, d]
[b, c, d]
[a, c, e]
[b, c, e]

Upvotes: 1

Related Questions