Home > Enterprise >  How to iterate through list of lists on demand?
How to iterate through list of lists on demand?

Time:09-17

As the title indicates, I am looking for a solution on how to implement the getNextCombination method shown in the code snippet below.

public class Test {

    private final List<List<String>> iterators;
    
    /**
     * @return null if no combination left
     */
    public String[] getNextCombination() {
        return null;
    }
}

Assuming the attribute iterator would look like

[0]={1,2} 
[1]={4,5} 
[2]={7,8}

then the sequential call to the getNextCombination method should return something similar to this:

147
148
157
158
247
248
...
NULL

How can this be achieved as efficient as possible?

Thanks for any help.

CodePudding user response:

You can do something like this :

private final List<List<String>> iterators = Arrays.asList(Arrays.asList("1", "2"), Arrays.asList("4", "5"), Arrays.asList("7", "8"));

public void run() {
    one("", 0, 0, iterators.size(), iterators.get(0).size()); // begin the iterate over
}

public void iterateOver(String before, int x, int y, int maxX, int maxY) {
    List<String> secondList = iterators.get(x); // get Y value list
    secondList.forEach((ss) -> {
        if(x   1 < maxX) { // go to next line
            iterateOver(before   ss, x   1, 0, maxX, iterators.get(x   1).size());
        } else if(y   1 < maxY) {
            System.out.println(before   ss); // print result
            iterateOver(before   ss, x, y   1, maxX, maxY); // go to next Y value
        } else {
            // this is finished
        }
    });
}


Output:

147
148
157
158
247
248
257
258

Warns: Arrays#asList list are not editable.

Also, I didn't make the full getNextCombination method because now you can use this code as you want to implement your prefered way to be at a combination, and go to next.

CodePudding user response:

The following generic lazy function to calculate all combinations do the job and works for any number of lists or any other structure

static Stream<int []> combinationsStream(final int... numElems) {
    final int numCombinations = Arrays.stream(numElems)                 // the number of solutions is
            .map(n -> n   1)                                            // possibles sizes
            .reduce(1, (a, b) -> a * b);                                // product

    final int[] queue = Arrays.stream(numElems).map(i -> -1).toArray(); // for every item take n elements
    final int[] i = new int[]{0};                                       // volatile (final) integer
    final Function<int [], int []> next = o -> {                        // compute the next combination
        while (true) {
            if (i[0] == numElems.length) {                              // if is the last position
                i[0] -= 1;                                              // we will back
                return queue;                                           // but it's a combination
            }
            queue[i[0]]  = 1;                                           // take one more of i[0]-th element
            if (queue[i[0]] > numElems[i[0]])                           // if no more of this
                queue[i[0]--] = -1;                                     // reset and go back
            else
                i[0]  = 1;                                              // if not, go forward
        }
    };
    return Stream.iterate(null, next::apply)                            // iterate getting next over and over
            .skip(1)                                                    // avoid the seed (null)
            .limit(numCombinations);                                    // limit the stream to combs number
}

since only work with indexes, you can use as follows

Let your list:

// let you list of list (from somewhere)
List<List<String>> iterators = asList(
        asList("1", "2"),
        asList("4", "5"),
        asList("7", "8")
);

(lists could contains different number of elements)

then, to get a lazy stream with all combinations simply convert the list of list to an array of max indexes

// we get combinations in a lazy way simply with
// (you could set this stream in your private class field if you wish)
Stream<int []> lazyCombinations = combinationsStream(iterators.stream()
        .mapToInt(g -> g.size() - 1).toArray());

in your case the call will be combinationsStream(1, 1, 1) since valid indexes are 0, 1 for each list.

To convert some index combination to your String output get the indexes and join

// to convert the indexes array to values (and concatenate all) you can write the function
Function<int [], String> toString = indexes -> IntStream
        // for each list
        .range(0, indexes.length)
        // get the indexes[i]-th element
        .mapToObj(i -> iterators.get(i).get(indexes[i]))
        // and join
        .collect(joining());

then, simply map the previous Stream<int []> to Stream<String> using that function

// now you can convert your indexes stream to your final combinations stream (all lazy)
Stream<String> lazySolutions = lazyCombinations.map(toString);

you can use the Stream in many ways (i.e. return in a Web Service lazily) but, if you wish take one by one you can convert to an iterator.

The itarator is lazy too

// if you need peek combinations on demand (not in a streaming way) convert stream to iterator
Iterator<String> lazyIterator = lazySolutions.iterator();

Then, you can take only one

// you can take one by one (e.g. inside a `getNextCombination`)
System.out.println("Only one: "   lazyIterator.next());

or as many as you consider

// or to the end
lazyIterator.forEachRemaining(System.out::println);

with output

Only one: 147
148
157
158
247
248
257
258
  • Related