-
Notifications
You must be signed in to change notification settings - Fork 526
[COLLECTIONS-897] Add LexicographicPermutationIterator #721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hextriclosan
wants to merge
5
commits into
apache:master
Choose a base branch
from
hextriclosan:COLLECTIONS-897-LexicographicPermutationIterator
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+660
−0
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
04b05e7
[COLLECTIONS-897] Add LexicographicPermutationIterator
hextriclosan fb59954
Documentation improvements
hextriclosan 12278fa
bump-up to the next release version 4.7.0
hextriclosan 2a8bbeb
Improvements according to reviewer's comments
hextriclosan fd2e27e
Remove equals and hashCode
hextriclosan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
219 changes: 219 additions & 0 deletions
219
...main/java/org/apache/commons/collections4/iterators/LexicographicPermutationIterator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.commons.collections4.iterators; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.Comparator; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.NoSuchElementException; | ||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * This iterator creates permutations of an input collection, using the | ||
| * lexicographical order. | ||
| * <p> | ||
| * Iteration starts at the arrangement in which the elements are given, and each | ||
| * call to {@code next()} advances to the smallest arrangement greater than the | ||
| * current one. Iteration therefore ends at the largest arrangement, and the ones | ||
| * preceding the given arrangement are never returned: only a collection already | ||
| * sorted according to the ordering in use yields the complete set of | ||
| * permutations. Callers wanting the complete set must sort the collection | ||
| * beforehand, just as callers of | ||
| * {@link java.util.Collections#binarySearch(java.util.List, Object) binarySearch} | ||
| * must. Callers wanting to enumerate one part of the set, to resume from a | ||
| * previously reached arrangement or to split the work, may start anywhere. | ||
| * </p> | ||
| * <p> | ||
| * The starting arrangement is the iteration order of the given collection, so | ||
| * collections whose iteration order is unspecified, such as {@link java.util.HashSet}, | ||
| * make poor input: which permutations are returned is then unspecified too. | ||
| * </p> | ||
| * <p> | ||
| * The iterator might return fewer than n! permutations of the input collection, | ||
| * either because the collection was not sorted according to the ordering in use, | ||
| * as described above, or because duplicated permutations are skipped: equal | ||
| * elements are not distinguished from one another. | ||
| * The {@code remove()} operation is not supported, and will throw an | ||
| * {@code UnsupportedOperationException}. | ||
| * </p> | ||
| * <p> | ||
| * NOTE: in case an empty collection is provided, the iterator will | ||
| * return exactly one empty list as result, as 0! = 1. | ||
| * </p> | ||
| * <p> | ||
| * NOTE: {@link PermutationIterator} differs on both counts. It returns exactly n! | ||
| * permutations whatever the order of the input collection. The two iterators are | ||
| * therefore not interchangeable. | ||
| * </p> | ||
| * | ||
| * @param <E> the type of the objects being permuted | ||
| * @see PermutationIterator | ||
| * @since 4.7.0 | ||
| */ | ||
| public class LexicographicPermutationIterator<E> implements Iterator<List<E>> { | ||
|
|
||
| /** | ||
| * The comparator used to define order of generation, | ||
| * or null if it uses the natural ordering. | ||
| */ | ||
| private final Comparator<? super E> comparator; | ||
|
|
||
| /** | ||
| * Next permutation to return. When a permutation is requested a copy of this | ||
| * instance is provided and the next one is computed. | ||
| */ | ||
| private List<E> nextPermutation; | ||
|
|
||
| /** | ||
| * Standard constructor for this class, using the natural ordering of the elements. | ||
| * <p> | ||
| * Iteration starts at the arrangement in which the collection iterates its | ||
| * elements; sort the collection first to obtain the complete set of permutations. | ||
| * </p> | ||
| * | ||
| * @param collection The collection to generate permutations for | ||
| * @throws NullPointerException if collection is null | ||
| */ | ||
| public LexicographicPermutationIterator(final Collection<? extends E> collection) { | ||
| this(collection, null); | ||
| } | ||
|
|
||
| /** | ||
| * Constructs an instance using the given comparator to order the elements. | ||
| * <p> | ||
| * Iteration starts at the arrangement in which the collection iterates its | ||
| * elements; sort the collection with the same comparator first to obtain the | ||
| * complete set of permutations. | ||
| * </p> | ||
| * | ||
| * @param collection The collection to generate permutations for | ||
| * @param comparator The comparator used to define the order of generation, | ||
| * or null to use the natural ordering of the elements | ||
| * @throws NullPointerException if collection is null | ||
| */ | ||
| public LexicographicPermutationIterator(final Collection<? extends E> collection, final Comparator<? super E> comparator) { | ||
| Objects.requireNonNull(collection, "collection"); | ||
| nextPermutation = new ArrayList<>(collection); | ||
| this.comparator = comparator; | ||
| } | ||
|
|
||
| /** | ||
| * Indicates if there are more permutations available. | ||
| * | ||
| * @return true if there are more permutations, otherwise false | ||
| */ | ||
| @Override | ||
| public boolean hasNext() { | ||
| return nextPermutation != null; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the next permutation of the input collection. | ||
| * <p> | ||
| * The returned list is a mutable copy taken before the iterator advances, never the | ||
| * iterator's own state, and it belongs to the caller. It may therefore be modified in | ||
| * any way and at any time without affecting the remaining permutations. | ||
| * </p> | ||
| * | ||
| * @return A list of the permutator's elements representing a permutation | ||
| * @throws NoSuchElementException if there are no more permutations | ||
| * @throws ClassCastException if no comparator was supplied and the elements are | ||
| * not mutually {@link Comparable} | ||
| */ | ||
| @Override | ||
| public List<E> next() { | ||
| if (!hasNext()) { | ||
| throw new NoSuchElementException(); | ||
| } | ||
|
|
||
| final List<E> result = new ArrayList<>(nextPermutation); | ||
| nextPermutation = smallestGreaterThan(nextPermutation); | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Always throws {@link UnsupportedOperationException}. | ||
| * | ||
| * @throws UnsupportedOperationException Always thrown. | ||
| */ | ||
| @Override | ||
| public void remove() { | ||
| throw new UnsupportedOperationException("remove() is not supported"); | ||
| } | ||
|
|
||
| /** | ||
| * Compares two elements using the comparator, or their natural ordering if no | ||
| * comparator was supplied. | ||
| * | ||
| * @param e1 The first element to compare | ||
| * @param e2 The second element to compare | ||
| * @return a negative integer, zero, or a positive integer as the first element | ||
| * is less than, equal to, or greater than the second | ||
| * @throws ClassCastException if no comparator was supplied and the elements are | ||
| * not mutually {@link Comparable} | ||
| */ | ||
| @SuppressWarnings("unchecked") | ||
| private int compareElements(final E e1, final E e2) { | ||
| return comparator == null | ||
| ? ((Comparable<? super E>) e1).compareTo(e2) | ||
| : comparator.compare(e1, e2); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the smallest arrangement of the given elements greater than the given one, | ||
| * as a new list. The permutation passed in is only read, never rearranged. | ||
| * | ||
| * @param permutation The arrangement to advance from | ||
| * @return A new list holding the next arrangement in lexicographical order, or null | ||
| * if the given one is already the largest | ||
| * @throws ClassCastException if no comparator was supplied and the elements are | ||
| * not mutually {@link Comparable} | ||
| */ | ||
| private List<E> smallestGreaterThan(final List<E> permutation) { | ||
| final int size = permutation.size(); | ||
|
|
||
| // find the pivot: the rightmost element that is smaller than its successor. | ||
| // if there is none the given permutation is the last one in lexicographical order | ||
| int i = size - 2; | ||
| while (i >= 0 && compareElements(permutation.get(i), permutation.get(i + 1)) >= 0) { | ||
| --i; | ||
| } | ||
|
|
||
| if (i < 0) { | ||
| return null; | ||
| } | ||
|
|
||
| // find the rightmost element greater than the pivot; the tail is descending, | ||
| // so this is the pivot's successor in the remaining elements | ||
| int j = size - 1; | ||
| while (j >= i && compareElements(permutation.get(i), permutation.get(j)) >= 0) { | ||
| --j; | ||
| } | ||
|
|
||
| // swap the pivot with its successor, then reverse the descending tail | ||
| // into ascending order to obtain the smallest larger permutation | ||
| final List<E> nextP = new ArrayList<>(permutation); | ||
| Collections.swap(nextP, i, j); | ||
| final List<E> subList = nextP.subList(i + 1, nextP.size()); | ||
| Collections.reverse(subList); | ||
| return nextP; | ||
| } | ||
|
|
||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method returns the internal List reference that was used for the previous step.
PermutationIteratordoes the same, so the behavior is consistent with the existing code base, but it means callers receive a mutable list that is no longer referenced internally after the call. Would a defensive copy or unmodifiable view would be safer?Alternatively, the Javadoc could state that the returned
Listis mutable and should be treated as read-only.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to a defensive copy. Details in a separate comment.