在Java中获取集合元素的唯一对的习惯用法

在Java中获取集合元素的唯一对的习惯用法,java,collections,set,Java,Collections,Set,是否有一个标准的习惯用法来获取给定集合中每一对独特元素的集合 就我们的目的而言,(a,b)的集合相当于(b,a),因此结果集中只应出现一个 我可以看到如何使用Pair类构造这样一个集合,该类基于成对元素实现hashCode和equals(),但我想知道是否还有更标准的方法来生成这样一个集合。如您所说,实现了hashcode&equals并将其放置在HashSet中的Pair类将完成您所寻找的内容。我不知道JDK数据结构是以本机方式实现这一点的 如果您想进一步推广它,可以创建一个元组,Tuple,

是否有一个标准的习惯用法来获取给定集合中每一对独特元素的集合

就我们的目的而言,(a,b)的集合相当于(b,a),因此结果集中只应出现一个


我可以看到如何使用Pair类构造这样一个集合,该类基于成对元素实现hashCode和equals(),但我想知道是否还有更标准的方法来生成这样一个集合。

如您所说,实现了hashcode&equals并将其放置在HashSet中的Pair类将完成您所寻找的内容。我不知道JDK数据结构是以本机方式实现这一点的

如果您想进一步推广它,可以创建一个元组,
Tuple
,并基于该元组声明一个HashSet,
HashSet
。然后为元组类型创建一个更通用的Equals/Hashcode方法

下面是一个示例实现:

final class Pair<A, B> {
    private final A _first;
    private final B _second;

    public Pair(A first, B second) {
        _first = first;
        _second = second;
    }

    @Override
    public int hashCode() {
        int hashFirst = _first != null ? _first.hashCode() : 0;
        int hashSecond = _second != null ? _second.hashCode() : 0;
        return (hashFirst + hashSecond) * hashSecond + hashFirst;
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object other) {
        if (other instanceof Pair) {
            Pair otherPair = (Pair) other;
            return this._first == otherPair._first //
                    || (this._first != null //
                            && otherPair._first != null && this._first.equals(otherPair._first)) //

                    && this._second == otherPair._second //
                    || (this._second != null //
                            && otherPair._second != null && this._second.equals(otherPair._second));
        }
        return false;
    }

    @Override
    public String toString() {
        return "(" + _first + ", " + _second + ")"; 
    }

    public A getFirst() {
        return _first;
    }

    public B getSecond() {
        return _second;
    }
最终类对{
私人决赛A_优先;
私人决赛B_秒;
公共对(A第一,B第二){
_第一=第一;
_秒=秒;
}
@凌驾
公共int hashCode(){
int hashFirst=\u first!=null?\u first.hashCode():0;
int hashSecond=\u second!=null?\u second.hashCode():0;
返回(hashFirst+hashSecond)*hashSecond+hashFirst;
}
@凌驾
@抑制警告(“未选中”)
公共布尔等于(对象其他){
if(配对的其他实例){
配对其他配对=(配对)其他;
返回此项。_first==otherPair。_first//
||(此._first!=null//
&&otherPair.\u first!=null&&this.\u first.equals(otherPair.\u first))//
&&这个。_second==otherPair。_second//
||(此。_秒!=null//
&&otherPair.\u second!=null&&this.\u second.equals(otherPair.\u second));
}
返回false;
}
@凌驾
公共字符串toString(){
返回“(“+”第一个+“,“+”第二个+”);
}
公共A getFirst(){
首先返回;
}
公共B getSecond(){
返回秒;
}
我为您创建了一个“惰性”实现,更一般一些(即可以列出任何大小的子集)

它是懒惰的,因为它不需要在内存中同时使用原始集合(或所有子集)的所有元素,但它不是真正有效的:在迭代器上调用
next()
仍然意味着在原始集合上迭代
k-1
次(如果
k
是所需的子集大小)-幸运的是,不是每次,大多数时候我们只需要在一个迭代器上使用一个
next()
(至少当
k
n
小时,基集的大小)。当然,我们仍然需要在内存中保留子集的
k
元素

我们还必须假设所有迭代器使用相同的顺序,只要底层集合不变(并且我们不希望在其中一个迭代进行中改变它)。如果
迭代器
接口允许克隆迭代器,即在当前一个迭代器所在的位置启动第二个迭代器,则这将容易得多。(我们现在使用
scrollTo
方法实现这一点。)对于排序映射,这应该容易得多(等待更新)

package de.fenging_game.paul.examples;
导入java.util.*;
/**
*给定(有限)集的给定大小的所有子集的不可修改集。
*
*灵感来自http://stackoverflow.com/questions/5428417/idiom-for-getting-unique-pairs-of-collection-elements-in-java
*/
公共类有限子集
扩展抽象集{
专用最终集基集;
私人最终补贴;
/**
*创建给定集合的给定大小的所有子集的集合。
* 
*@param baseSet其子集应在此集中的集合。
*为了使其正常工作,基集的迭代器应该进行迭代
*每次按相同的顺序,只要迭代一次就可以了
*正在进行中。
*
*baseSet不需要是不可变的,但它不应该更改
*当此集合的迭代正在进行时。
*@param subSetSize
*/
公共有限子集(Set baseSet,int subSetSize){
this.baseSet=baseSet;
this.subSize=subSetSize;
}
/**
*计算此集合的大小。
*
*这是二项式系数。
*/
公共整数大小(){
int baseSize=baseSet.size();
长尺寸=双尺寸(基准尺寸、子尺寸);
返回(int)Math.min(大小、整数、最大值);
}
公共迭代器迭代器(){
如果(子项==0){
//我们的迭代器MPL不适用于k==0。
返回Collections.singleton(Collections.emptySet()).iterator();
}
返回新的IteratorImpl();
}
/**
*检查某个对象是否在此集中。
*
*此实现与其他实现相比是优化的
*抽象集的实现。
*/
公共布尔包含(对象o){
返回集合&&
((设置)o).size()=子版&&
baseSet.containsAll((Set)o);
}
/**
*迭代器的实现。
*/
私有类迭代器MPL实现迭代器{
/**
*基集合上的迭代器堆栈。
*我们只操纵上面的,下面的
*在顶级联赛结束后。
*堆栈应该总是满的,除非在构造函数或
*在{@link#step}方法内部。
*/
私有数据队列堆栈=新的阵列队列(子队列);
/**
*保持迭代当前状态的链表,即。
*下一个子集。没有下一个子集时为空,但
*否则它
package de.fencing_game.paul.examples;

import java.util.*;

/**
 * An unmodifiable set of all subsets of a given size of a given (finite) set.
 *
 * Inspired by http://stackoverflow.com/questions/5428417/idiom-for-getting-unique-pairs-of-collection-elements-in-java
 */
public class FiniteSubSets<X>
    extends AbstractSet<Set<X>> {

    private final Set<X> baseSet;
    private final int subSize;

    /**
     * creates a set of all subsets of a given size of a given set.
     * 
     * @param baseSet the set whose subsets should be in this set.
     * For this to work properly, the baseSet's iterators should iterate
     * every time in the same order, as long as one iteration of this set
     * is in progress.
     *
     * The baseSet does not need to be immutable, but it should not change
     * while an iteration of this set is in progress.
     * @param subSetSize
     */
    public FiniteSubSets(Set<X> baseSet, int subSetSize) {
        this.baseSet = baseSet;
        this.subSize = subSetSize;
    }

    /**
     * calculates the size of this set.
     *
     * This is the binomial coefficient.
     */
    public int size() {
        int baseSize = baseSet.size();
        long size = binomialCoefficient(baseSize, subSize);
        return (int)Math.min(size, Integer.MAX_VALUE);
    }

    public Iterator<Set<X>> iterator() {
        if(subSize == 0) {
            // our IteratorImpl does not work for k == 0.
            return Collections.singleton(Collections.<X>emptySet()).iterator();
        }
        return new IteratorImpl();
    }

    /**
     * checks if some object is in this set.
     *
     * This implementation is optimized compared to 
     * the implementation from AbstractSet.
     */
    public boolean contains(Object o) {
        return o instanceof Set && 
            ((Set)o).size() == subSize &&
            baseSet.containsAll((Set)o);
    }


    /**
     * The implementation of our iterator.
     */
    private class IteratorImpl implements Iterator<Set<X>> {

        /**
         * A stack of iterators over the base set.
         * We only ever manipulate the top one, the ones below only
         * after the top one came to its end.
         * The stack should be always full, except in the constructor or
         * inside the {@link #step} method.
         */
        private Deque<Iterator<X>> stack = new ArrayDeque<Iterator<X>>(subSize);
        /**
         * a linked list maintaining the current state of the iteration, i.e.
         * the next subset. It is null when there is no next subset, but
         * otherwise it should have always full length subSize (except when
         * inside step method or constructor).
         */
        private Node current;

        /**
         * constructor to create the stack of iterators and initial
         * node.
         */
        IteratorImpl() {
            try {
                for(int i = 0; i < subSize; i++) {
                    initOneIterator();
                }
            }
            catch(NoSuchElementException ex) {
                current = null;
            }
            //      System.err.println("IteratorImpl() End, current: " + current);
        }

        /**
         * initializes one level of iterator and node.
         * Only called from the constructor.
         */
        private void initOneIterator() {
            Iterator<X> it = baseSet.iterator();
            if(current != null) {
                scrollTo(it, current.element);
            }

            X element = it.next();
            current = new Node(current, element);
            stack.push(it);
        }

        /**
         * gets the next element from the set (i.e. the next
         * subset of the base set).
         */
        public Set<X> next() {
            if(current == null) {
                throw new NoSuchElementException();
            }
            Set<X> result = new SubSetImpl(current);
            step();
            return result;
        }

        /**
         * returns true if there are more elements.
         */
        public boolean hasNext() {
            return current != null;
        }

        /** throws an exception. */
        public void remove() {
            throw new UnsupportedOperationException();
        }


        /**
         * Steps the iterator on the top of the stack to the
         * next element, and store this in {@link #current}.
         *
         * If this iterator is already the end, we recursively
         * step the iterator to the next element.
         *
         * If there are no more subsets at all, we set {@link #current}
         * to null.
         */
        void step() {
            Iterator<X> lastIt = stack.peek();
            current = current.next;
            while(!lastIt.hasNext()) {
                if(current == null) {
                    // no more elements in the first level iterator
                    // ==> no more subsets at all.
                    return;
                }

                // last iterator has no more elements
                // step iterator before and recreate last iterator.
                stack.pop();
                assert current != null;
                step();
                if(current == null) {
                    // after recursive call ==> at end of iteration.
                    return;
                }
                assert current != null;

                // new iterator at the top level
                lastIt = baseSet.iterator();
                if(!scrollTo(lastIt, current.element)) {
                    // Element not available anymore => some problem occured
                    current = null;
                    throw new ConcurrentModificationException
                        ("Element " + current.element + " not found!");
                }
                stack.push(lastIt);
            }
            // now we know the top iterator has more elements
            // ==> put the next one in `current`.

            X lastElement = lastIt.next();
            current = new Node(current, lastElement);

        }  // step()

    }

    /**
     * helper method which scrolls an iterator to some element.
     * @return true if the element was found, false if we came
     *    to the end of the iterator without finding the element.
     */
    private static <Y> boolean scrollTo(Iterator<? extends Y> it, Y element) {
        while(it.hasNext()) {
            Y itEl = it.next();
            if(itEl.equals(element)) {
                return true;
            }
        }
        return false;
    }

    /**
     * implementation of our subsets.
     * These sets are really immutable (not only unmodifiable).
     *
     * We implement them with a simple linked list of nodes.
     */
    private class SubSetImpl extends AbstractSet<X>
    {
        private final Node node;

        SubSetImpl(Node n) {
            this.node = n;
        }

        /**
         * the size of this set.
         */
        public int size() {
            return subSize;
        }

        /**
         * an iterator over our linked list.
         */
        public Iterator<X> iterator() {
            return new Iterator<X>() {
                private Node current = SubSetImpl.this.node;
                public X next() {
                    if(current == null) {
                        throw new NoSuchElementException();
                    }
                    X result = current.element;
                    current = current.next;
                    return result;
                }
                public boolean hasNext() {
                    return current != null;
                }
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    }

    /**
     * A simple immutable node class, used to implement our iterator and
     * the sets created by them.
     *
     * Two "neighbouring" subsets (i.e. which
     * only differ by the first element) share most of the linked list,
     * differing only in the first node.
     */
    private class Node {
        Node(Node n, X e) {
            this.next = n;
            this.element = e;
        }

        final X element;
        final Node next;

        public String toString() {
            return "[" + element + "]==>" + next;
        }
    }

    /**
     * Calculates the binomial coefficient B(n,k), i.e.
     * the number of subsets of size k in a set of size n.
     *
     * The algorithm is taken from the <a href="http://de.wikipedia.org/wiki/Binomialkoeffizient#Algorithmus_zur_effizienten_Berechnung">german wikipedia article</a>.
     */
    private static long binomialCoefficient(int n, int k) {
        if(k < 0 || n < k ) {
            return 0;
        }
        final int n_minus_k = n - k;
        if (k > n/2) {
            return binomialCoefficient(n, n_minus_k);
        }
        long prod = 1;
        for(int i = 1; i <= k; i++) {
            prod = prod * (n_minus_k + i) / i;
        }
        return prod;
    }


    /**
     * Demonstrating test method. We print all subsets (sorted by size) of
     * a set created from the command line parameters, or an example set, if
     * there are no parameters.
     */
    public static void main(String[] params) {
        Set<String> baseSet =
            new HashSet<String>(params.length == 0 ?
                                Arrays.asList("Hello", "World", "this",
                                              "is", "a", "Test"):
                                Arrays.asList(params));


        System.out.println("baseSet: " + baseSet);

        for(int i = 0; i <= baseSet.size()+1; i++) {
            Set<Set<String>> pSet = new FiniteSubSets<String>(baseSet, i);
            System.out.println("------");
            System.out.println("subsets of size "+i+":");
            int count = 0;
            for(Set<String> ss : pSet) {
                System.out.println("    " +  ss);
                count++;
            }
            System.out.println("in total: " + count + ", " + pSet.size());
        }
    }


}