【问题标题】:How to create combinations of values in Java?如何在 Java 中创建值组合?
【发布时间】:2016-06-27 22:25:09
【问题描述】:

我有以下地图:Map<Integer,String[]> map = new HashMap<Integer,String[]>();

键是整数,值是数组(也可以用列表代替)。

现在,我想获取键中值的所有可能组合。例如,假设地图包含以下条目:

key 1: "test1", "stackoverflow"
key 2: "test2", "wow"
key 3: "new"

组合包括

("test1","test2","new")
("test1","wow","new")
("stackoverflow", "test2", "new")
("stackoverflow", "wow", "new")

为此,我设想了一个方法 boolean hasNext(),如果存在下一对,则返回 true,而第二个方法仅返回下一组值(如果有)。

如何做到这一点?该地图也可以被其他数据结构替换。

【问题讨论】:

  • 这可以使用递归来实现,但是如何......这仍然是一个有待回答的问题......
  • Nahh :) 您可以轻松地做到这一点而无需递归。只需计算一个“变量”基数。

标签: java dictionary hashmap iterator


【解决方案1】:

我将此作为一个挑战,看看新的 Java 8 API 是否有助于解决这类问题。所以这是我的解决方案:

public class CombinatorIterator implements Iterator<Collection<String>> {
    private final String[][] arrays;
    private final int[] indices;
    private final int total;
    private int counter;

    public CombinatorIterator(Collection<String[]> input) {
        arrays = input.toArray(new String[input.size()][]);
        indices = new int[arrays.length];
        total = Arrays.stream(arrays).mapToInt(arr -> arr.length)
                .reduce((x, y) -> x * y).orElse(0);
        counter = 0;
    }

    @Override
    public boolean hasNext() {
        return counter < total;
    }

    @Override
    public Collection<String> next() {
        List<String> nextValue = IntStream.range(0, arrays.length)
                .mapToObj(i -> arrays[i][indices[i]]).collect(Collectors.toList());

        //rolling carry over the indices
        for (int j = 0; 
                j < arrays.length && ++indices[j] == arrays[j].length; j++) {
            indices[j] = 0;
        }

        counter++;
        return nextValue;
    }
}

请注意,我不使用地图作为输入,因为地图键实际上在这里没有任何作用。您可以使用map.values() 来传递迭代器的输入。使用以下测试代码:

List<String[]> input = Arrays.asList(
    new String[] {"such", "nice", "question"},
    new String[] {"much", "iterator"},
    new String[] {"very", "wow"}
);
Iterator<Collection<String>> it = new CombinatorIterator(input);
it.forEachRemaining(System.out::println);

输出将是:

[such, much, very]
[nice, much, very]
[question, much, very]
[such, iterator, very]
[nice, iterator, very]
[question, iterator, very]
[such, much, wow]
[nice, much, wow]
[question, much, wow]
[such, iterator, wow]
[nice, iterator, wow]
[question, iterator, wow]

【讨论】:

    【解决方案2】:

    该算法本质上与十进制数的增量算法(“x -> x+1”)几乎相同。

    这里是迭代器类:

    import java.util.Iterator;
    import java.util.Map;
    import java.util.NoSuchElementException;
    import java.util.TreeSet;
    
    public class CombinationsIterator implements Iterator<String[]> {
    
        // Immutable fields
        private final int combinationLength;
        private final String[][] values;
        private final int[] maxIndexes;
    
        // Mutable fields
        private final int[] currentIndexes;
        private boolean hasNext;
    
        public CombinationsIterator(final Map<Integer,String[]> map) {
            combinationLength = map.size();
            values = new String[combinationLength][];
            maxIndexes = new int[combinationLength];
            currentIndexes = new int[combinationLength];
    
            if (combinationLength == 0) {
                hasNext = false;
                return;
            }
    
            hasNext = true;
    
            // Reorganize the map to array.
            // Map is not actually needed and would unnecessarily complicate the algorithm.
            int valuesIndex = 0;
            for (final int key : new TreeSet<>(map.keySet())) {
                values[valuesIndex++] = map.get(key);
            }
    
            // Fill in the arrays of max indexes and current indexes.
            for (int i = 0; i < combinationLength; ++i) {
                if (values[i].length == 0) {
                    // Set hasNext to false if at least one of the value-arrays is empty.
                    // Stop the loop as the behavior of the iterator is already defined in this case:
                    // the iterator will just return no combinations.
                    hasNext = false;
                    return;
                }
    
                maxIndexes[i] = values[i].length - 1;
                currentIndexes[i] = 0;
            }
        }
    
        @Override
        public boolean hasNext() {
            return hasNext;
        }
    
        @Override
        public String[] next() {
            if (!hasNext) {
                throw new NoSuchElementException("No more combinations are available");
            }
            final String[] combination = getCombinationByCurrentIndexes();
            nextIndexesCombination();
            return combination;
        }
    
        private String[] getCombinationByCurrentIndexes() {
            final String[] combination = new String[combinationLength];
            for (int i = 0; i < combinationLength; ++i) {
                combination[i] = values[i][currentIndexes[i]];
            }
            return combination;
        }
    
        private void nextIndexesCombination() {
            // A slightly modified "increment number by one" algorithm.
    
            // This loop seems more natural, but it would return combinations in a different order than in your example:
    //      for (int i = 0; i < combinationLength; ++i) {
    
            // This loop returns combinations in the order which matches your example:
            for (int i = combinationLength - 1; i >= 0; --i) {
                if (currentIndexes[i] < maxIndexes[i]) {
                    // Increment the current index
                    ++currentIndexes[i];
                    return;
                } else {
                    // Current index at max: 
                    // reset it to zero and "carry" to the next index
                    currentIndexes[i] = 0;
                }
            }
            // If we are here, then all current indexes are at max, and there are no more combinations
            hasNext = false;
        }
    
        @Override
        public void remove() {
            throw new UnsupportedOperationException("Remove operation is not supported");
        }
    
    }
    

    这里是示例用法:

    final Map<Integer,String[]> map = new HashMap<Integer,String[]>();
    map.put(1, new String[]{"test1", "stackoverflow"});
    map.put(2, new String[]{"test2", "wow"});
    map.put(3, new String[]{"new"});
    
    final CombinationsIterator iterator = new CombinationsIterator(map);
    while (iterator.hasNext()) {
        System.out.println(
            org.apache.commons.lang3.ArrayUtils.toString(iterator.next())
        );
    }
    

    它会准确打印您的示例中指定的内容。


    附:地图实际上是不需要的;它可以被一个简单的数组(或列表列表)替换。然后构造函数会变得更简单一些:

    public CombinationsIterator(final String[][] array) {
        combinationLength = array.length;
        values = array;
    
        // ...
    
        // Reorganize the map to array - THIS CAN BE REMOVED.
    

    【讨论】:

    • 非常感谢,亚历克斯。这段代码很棒。完美运行。
    猜你喜欢
    • 2020-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多