4
\$\begingroup\$

(See the next iteration here.)

Intro

This time, I have an Iterator<List<Integer>> implementation that generates list index tuples in lexicographic order. The sample main() output for new IndexTupleIterator(3, 6) is:

1:  [0, 1, 2]
2:  [0, 1, 3]
3:  [0, 1, 4]
4:  [0, 1, 5]
5:  [0, 2, 3]
6:  [0, 2, 4]
7:  [0, 2, 5]
8:  [0, 3, 4]
9:  [0, 3, 5]
10: [0, 4, 5]
11: [1, 2, 3]
12: [1, 2, 4]
13: [1, 2, 5]
14: [1, 3, 4]
15: [1, 3, 5]
16: [1, 4, 5]
17: [2, 3, 4]
18: [2, 3, 5]
19: [2, 4, 5]
20: [3, 4, 5]

In general, for a list of \$n > 0\$ elements and index tuple length \$k \in \{1, \ldots, n \}\$, the total number of index tuples will be presicely

$$\binom{n}{k}.$$

Code

com.github.coderodde.util.IndexTupleIterator.java:

package com.github.coderodde.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Consumer;

/**
 * This class implements an iterator that generates index tuples in 
 * lexicographic order.
 * 
 * @version 1.0.0
 * @since 1.0.0
 */
public final class IndexTupleIterator implements Iterator<List<Integer>>{

    /**
     * The length of the index tuples.
     */
    private final int indexTupleLength;
    
    /**
     * The length of the list for which the indices are being generated.
     */
    private final int lengthOfTargetList;
    
    /**
     * The actual internal state containing the current index tuple.
     */
    private final List<Integer> indices;
    
    /**
     * The number of iterated tuples.
     */
    private long numberOfIteratedIndexTuples = 0L;
    
    /**
     * The total number of iterations.
     */
    private long totalNumberOfIterations;
    
    public IndexTupleIterator(final int indexTupleLength,
                              final int lengthOfTargetList) {
        checkArguments(indexTupleLength, 
                       lengthOfTargetList);
        
        this.indexTupleLength = indexTupleLength;
        this.lengthOfTargetList = lengthOfTargetList;
        this.indices = new ArrayList<>(indexTupleLength);
        
        initState();
    }

    @Override
    public boolean hasNext() {
        return numberOfIteratedIndexTuples < totalNumberOfIterations;
    }

    @Override
    public List<Integer> next() {
        if (!hasNext()) {
            throw new NoSuchElementException("Nothing to iterate.");
        }
        
        numberOfIteratedIndexTuples++;
        
        if (indices.get(indices.size() - 1) < lengthOfTargetList - 1) {
            indices.set(indices.size() - 1, 
                        indices.get(indices.size() - 1) + 1);
            
            return Collections.<Integer>unmodifiableList(indices);
        }
        
        for (int i = indices.size() - 2; i >= 0; --i) {
            final Integer a = indices.get(i);
            final Integer b = indices.get(i + 1);
            
            if (a < b - 1) {
                indices.set(i, indices.get(i) + 1);
                
                for (int j = i + 1; j < indices.size(); j++) {
                    indices.set(j, indices.get(j - 1) + 1);
                }
                
                return Collections.<Integer>unmodifiableList(indices);
            }
        }
        
        throw new IllegalStateException("Should not get here.");
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException(
                "remove() is not supported in IndexTupleIterator.");
    }

    @Override
    public void forEachRemaining(Consumer<? super List<Integer>> action) {
        throw new UnsupportedOperationException(
                "forEachRemainig() is not supported in IndexTupleIterator.");
    }
    
    public static void main(String[] args) {
        int lineNumber = 1;
        
        for (final List<Integer> list : new IndexTupleIterable(3, 6)) {
            System.out.printf("%d: %s\n", lineNumber++, list);
        }
    }
    
    private void initState() {
        // Create the initial index tuple <0, 1, ..., indexTupleLength - 1>:
        for (int i = 0; i < indexTupleLength; ++i) {
            this.indices.add(i);
        }
        
        // Compute the total number of iterations:
        final long factorialN = factorial(lengthOfTargetList);
        final long factorialK = factorial(indexTupleLength);
        final long factorialNminusK = 
                factorial(lengthOfTargetList - indexTupleLength);
        
        this.totalNumberOfIterations = factorialN 
                                     / factorialK 
                                     / factorialNminusK;
        
        // We need this in order to generate the first index tuple:
        this.indices.set(indexTupleLength - 1,
                         this.indices.get(indexTupleLength - 1) - 1);
    }
    
    /**
     * Checks that the input arguments are sensible.
     * 
     * @param indexTupleLength   the length of the index tuple.
     * @param lengthOfTargetList the length of the list being indexed.
     */
    private static void checkArguments(final int indexTupleLength,
                                       final int lengthOfTargetList) {
        if (indexTupleLength < 1) {
            final String exceptionMessage = 
                    String.format("indexTupleLength(%d) < 1",
                                  indexTupleLength);
            
            throw new IllegalArgumentException(exceptionMessage);
        }
        
        
        if (lengthOfTargetList < 1) {
            final String exceptionMessage = 
                    String.format("lengthOfTargetList(%d) < 1",
                                  lengthOfTargetList);
            
            throw new IllegalArgumentException(exceptionMessage);
        }
        
        if (indexTupleLength > lengthOfTargetList) {
            
            final String exceptionMessage = 
                    String.format(
                            "indexTupleLength(%d) > lengthOfTargetList(%d)",                            
                            indexTupleLength,
                            lengthOfTargetList);
            
            throw new IllegalArgumentException(exceptionMessage);
        }
    }
    
    private static long factorial(final long n) {
        long factorial = 1;
        
        for (long l = 1; l <= n; ++l) {
            factorial *= l;
        }
        
        return factorial;
    }
}

com.github.coderodde.util.IndexTupleIterable.java:

package com.github.coderodde.util;

import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;

/**
 * This class provides an iterable for iterating over list index tuples.
 * 
 * @version 1.0.0
 * @since 1.0.0
 */
public final class IndexTupleIterable implements Iterable<List<Integer>> {

    /**
     * The length of the index tuples.
     */
    private final int indexTupleLength;
    
    /**
     * The length of the list for which the indices are being generated.
     */
    private final int lengthOfTargetList;
    
    public IndexTupleIterable(final int indexTupleLength,
                              final int lengthOfTargetList) {
        this.indexTupleLength = indexTupleLength;
        this.lengthOfTargetList = lengthOfTargetList;
    }
    
    @Override
    public Iterator<List<Integer>> iterator() {
        return new IndexTupleIterator(indexTupleLength,
                                      lengthOfTargetList);
    }

    @Override
    public Spliterator<List<Integer>> spliterator() {
        throw new UnsupportedOperationException(
                "spliterator() is not supported in IndexTupleIterable.");
    }    
}

Critique request

As always, I am eager to hear any constructive criticism on my attempt.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ What do you use this for? \$\endgroup\$ Commented Jun 9 at 7:17
  • 1
    \$\begingroup\$ using long to calculate factorial will work only for values n <= 20. for larger values, you will need to use BigInteger. see details baeldung.com/java-calculate-factorial \$\endgroup\$ Commented Jun 9 at 10:32
  • 1
    \$\begingroup\$ @TorbenPutkonen Used it on several occasions. Got the job done. \$\endgroup\$ Commented Jun 9 at 17:05
  • 1
    \$\begingroup\$ @coderodde I didn't mean if it gets the job done. I meant the context in which it is used. It is somewhat important for the review. \$\endgroup\$ Commented Jun 10 at 4:08
  • 1
    \$\begingroup\$ @TorbenPutkonen I will add the context soon. \$\endgroup\$ Commented Jun 10 at 7:59

1 Answer 1

3
\$\begingroup\$

You don't need to use the factorial function and deal with long (which, as stated in a comment, will overflow for n > 20).

You know that the first entry is [0, 1, 2].

You know that the last entry is [3, 4, 5].

Use that knowledge instead to rewrite the hasNext() method.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.