简体   繁体   English

唯一包含一个键但在不同字段上排序的集合

[英]Set that uniquely contains a key but ordered on different field

I'm looking for a Java collection, possibly in standard library, which is able to collect the following structure:我正在寻找一个 Java 集合,可能在标准库中,它能够收集以下结构:

class Item {
    String key;
    double score;
}

And with the following properties:并具有以下属性:

  • Only one Item with the same key is allowed (like a set)只允许一个具有相同键的项目(如一组)
  • insert, remove, check existance in max O(logn)在最大 O(logn) 中插入、删除、检查是否存在
  • traversal ordered by score, finding next in max O(logn)按分数排序的遍历,在最大 O(logn) 中找到下一个

As far as I understood standard OrderedSet must have comparable interface coherent with equals() interface, but that is not my case as two items with different key may have the same score.据我所知,标准 OrderedSet 必须具有与 equals() 接口一致的可比较接口,但我的情况并非如此,因为具有不同键的两个项目可能具有相同的分数。

In fact I've notice that TreeSet uses the comparator returning 0 to check if the item is already present.事实上,我注意到 TreeSet 使用返回 0 的比较器来检查该项目是否已经存在。

Any suggestion?有什么建议吗?

I don't think such a structure exists.我不认为存在这样的结构。 You didn't specify traversal performance requirements, so you could use a normal Set and add the values to a list and sort that list by score for traversal.您没有指定遍历性能要求,因此您可以使用普通的 Set 并将值添加到列表中,然后按分数对该列表进行排序以进行遍历。

A HashSet does not guarantee any order of its elements. HashSet 不保证其元素的任何顺序。 If you need this guarantee, consider using a TreeSet to hold your elements but for achieving the unique by key and maintain constant time override hashCode() and equals() effectively what you looking for as the below:如果您需要此保证,请考虑使用 TreeSet 来保存您的元素,但要通过键实现唯一性并保持恒定时间有效地覆盖hashCode()equals() ,如下所示:

class Item {
    String key;
    double score;

    public Item(String key, double score) {
        this.key = key;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Item item = (Item) o;
        return key.equals(item.key);
    }

    @Override
    public int hashCode() {
        return Objects.hash(key);
    }

    @Override
    public String toString() {
        return "Item{" +
                "key='" + key + '\'' +
                ", score=" + score +
                '}';
    }
}

// main
public static void main(String[] args) {

        Set<Item> itemSet = new HashSet<>();

        itemSet.add(new Item("1", 1));
        itemSet.add(new Item("1", 2));
        itemSet.add(new Item("2", 1));

        //to get a sorted TreeSet
        //Add all your objects to the TreeSet, you will get a sorted Set.
        //TreeSet myTreeSet = new TreeSet();
        //myTreeSet.addAll(itemSet);
        //System.out.println(myTreeSet);
}

output: output:

Item{key='1', score=1.0}
Item{key='2', score=1.0}

Thanks to people that made me think with their comments and answers.感谢那些让我思考他们的评论和答案的人。 I believe we can achieve the requirements by using:我相信我们可以通过使用来达到要求:

TreeMap<Double, HashSet<Item>>

Just because (I haven't said that) two equal keys yield the same score;只是因为(我没说过)两个相同的键产生相同的分数; but more in general it's enough to have two maps of set: one (ordered) with ordering field as key, and one (not ordered) with unique field as key.但更一般地说,有两个集合映射就足够了:一个(有序)以排序字段为键,另一个(未排序)以唯一字段为键。

Now that insert has been relaxed to O(log n) , you can do this with a double-set, ie implement your own set that maintains 2 sets behind the scenes.现在插入已放宽到O(log n) ,您可以使用双集来执行此操作,即实现您自己的集,在幕后维护 2 个集。

Best would be if you can modify class Item to implement equals() and hashCode() to only use the key field.如果您可以修改 class Item以实现equals()hashCode()以仅使用key字段,那将是最好的。 In that case your class would use a HashSet and a TreeSet .在那种情况下,您的 class 将使用HashSetTreeSet If hashCode() covers more than just the key field, then use two TreeSet objects.如果hashCode()涵盖的不仅仅是key段,则使用两个TreeSet对象。

final class ItemSet implements NavigableSet<Item> {

    private final Set<Item> keySet = new HashSet<>();
    //                           or: new TreeSet<>(Comparator.comparing(Item::getKey));
    private final TreeSet<Item> navSet = new TreeSet<>(Comparator.comparingDouble(Item::getScore)
                                                                 .thenComparing(Item::getKey));

    //
    // Methods delegating to keySet for unique key access and for unordered access
    //

    @Override public boolean contains(Object o) { return this.keySet.contains(o); }
    @Override public boolean containsAll(Collection<?> c) { return this.keySet.containsAll(c); }
    @Override public int size() { return this.keySet.size(); }
    @Override public boolean isEmpty() { return this.keySet.isEmpty(); }

    //
    // Methods delegating to navSet for ordered access
    //

    @Override public Comparator<? super Item> comparator() { return this.navSet.comparator(); }
    @Override public Object[] toArray() { return this.navSet.toArray(); }
    @Override public <T> T[] toArray(T[] a) { return this.navSet.toArray(a); }
    @Override public Item first() { return this.navSet.first(); }
    @Override public Item last() { return this.navSet.last(); }
    @Override public Item lower(Item e) { return this.navSet.lower(e); }
    @Override public Item floor(Item e) { return this.navSet.floor(e); }
    @Override public Item ceiling(Item e) { return this.navSet.ceiling(e); }
    @Override public Item higher(Item e) { return this.navSet.higher(e); }

    //
    // Methods delegating to both keySet and navSet for mutation of this set
    //

    private final class ItemSetIterator implements Iterator<Item> {
        private final Iterator<Item> iterator = ItemSet.this.navSet.iterator();
        private Item keyToRemove;
        @Override
        public boolean hasNext() {
            return iterator.hasNext();
        }
        @Override
        public Item next() {
            keyToRemove = iterator.next();
            return keyToRemove;
        }
        @Override
        public void remove() {
            iterator.remove();
            ItemSet.this.keySet.remove(keyToRemove);
            keyToRemove = null;
        }
    }

    @Override
    public Iterator<Item> iterator() {
        return new ItemSetIterator();
    }
    @Override
    public void clear() {
        this.keySet.clear();
        this.navSet.clear();
    }
    @Override
    public boolean add(Item e) {
        if (! this.keySet.add(e))
            return false; // item already in set
        if (! this.navSet.add(e))
            throw new IllegalStateException("Internal state is corrupt");
        return true;
    }
    @Override
    public boolean remove(Object o) {
        if (! this.keySet.remove(o))
            return false; // item not in set
        if (! this.navSet.remove(o))
            throw new IllegalStateException("Internal state is corrupt");
        return true;
    }
    @Override
    public boolean addAll(Collection<? extends Item> c) {
        boolean changed = false;
        for (Item item : c)
            if (add(item))
                changed = true;
        return changed;
    }
    @Override
    public boolean removeAll(Collection<?> c) {
        boolean changed = false;
        for (Object o : c)
            if (remove(o))
                changed = true;
        return changed;
    }
    @Override
    public boolean retainAll(Collection<?> c) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public Item pollFirst() {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public Item pollLast() {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public NavigableSet<Item> descendingSet() {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public Iterator<Item> descendingIterator() {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public SortedSet<Item> headSet(Item toElement) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public NavigableSet<Item> headSet(Item toElement, boolean inclusive) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public SortedSet<Item> tailSet(Item fromElement) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public NavigableSet<Item> tailSet(Item fromElement, boolean inclusive) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public SortedSet<Item> subSet(Item fromElement, Item toElement) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public NavigableSet<Item> subSet(Item fromElement, boolean fromInclusive, Item toElement, boolean toInclusive) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

}

Only one Item with the same key is allowed (like a set)只允许一个具有相同键的项目(如一组)

Your Item class should implement hashCode() and equals() using just the key attribute.您的Item class 应该仅使用key属性实现 hashCode() 和 equals()。

insert, remove, check existance in constant time在恒定时间内插入,删除,检查是否存在

TreeSet add() and remove() are O(ln N), so they do not meet your criteria. TreeSet add() 和 remove() 是 O(ln N),因此它们不符合您的条件。

HashSet add() and remove() usually are O(1). HashSet add() 和 remove() 通常是 O(1)。

traversal ordered by score按分数排序的遍历

What are your performance requirements here?您在这里的性能要求是什么? How frequently will you be traversing the collection?您将多久遍历一次集合? If you will be mainly adding and removing the items and rarely traversing it, then you can make a copy of a HashSet to a TreeSet during the traversing operation.如果您主要是添加和删除项目而很少遍历它,那么您可以在遍历操作期间将HashSet复制到TreeSet

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM