在对象更改值时保持TreeSet排序


75

我有一个使用Comparable <>定义“自然排序顺序”的对象。这些都存储在TreeSet中。

除了删除和重新添加对象之外,当用于定义排序顺序的成员更新时,还有另一种方法可以更新排序吗?


这仅仅是病态的好奇心,还是您正在寻找特定的好处,比如说性能或代码简单性?
greim 2010年

2
我一直在寻找一种简单的无创方式来维护集合的排序顺序,因为事件会更新其中的对象。更改成员资格会对集合的其他消费者产生副作用。IMO触发元素的重新排序似乎是已排序集合的基本功能。
Stevko 2010年

GUI存在相同的问题,而MVC是解决方案。周期性地与您的TreeSet呼叫相对应的GUI update,或者如果值发生更改,则控制器(观察者模式)将由模型触发
Mike

受体系结构精确指责。TreeSet是我的DOT模型,它侦听websocket jms事件,然后通过演示者/控制器层中继模型更新以查看小部件。
Stevko

Answers:


16

正如其他人指出的那样,没有内置方法。但是,您始终可以使用您选择的构造函数将该TreeSet子类化,并添加所需的功能:

public class UpdateableTreeSet<T extends Updateable> extends TreeSet<T> {

    // definition of updateable
    interface Updateable{ void update(Object value); }

    // constructors here
    ...

    // 'update' method; returns false if removal fails or duplicate after update
    public boolean update(T e, Object value) {
       if (remove(e)) {
           e.update(value);
           return add(e);
       } else { 
           return false;
       }
    }
}

从那时起,您将必须调用((UpdateableTreeSet)mySet).update(anElement, aValue)以更新排序值和排序本身。这确实需要您update()在数据对象中实现其他方法。


5

我遇到了类似的问题,找到了这个线程和tucuxi的答案(谢谢!),在此基础上我实现了自己的UpdateableTreeSet。我的版本提供了

  • 遍历这样的集合,
  • 在循环内调度(延迟)元素更新/删除
  • 不必创建集合的临时副本,最后
  • 循环结束后,以批量操作的形式进行所有更新/删除。

UpdateableTreeSet向用户隐藏了很多复杂性。除了延迟的批量更新/删除,tucuxi所示的单元素更新/删除在该类中仍然可用。

2012年8月7日更新:该类可在一个小的GitHub存储库中使用,其中包括一个自述性自述文件,其中包含原理图示例代码以及单元测试,这些单元测试显示了(不)更详细地使用它。


我喜欢这个主意-它只是意味着将“ deferred-update”数组放置在UpdateableTreeSet中,然后调用“ do-deferred-updates”首先删除所有更新,然后重新插入它们。
tucuxi 2012年

正如我的代码示例所暗示的,这就是我所做的。这不仅仅是一个想法,而是一个真正的实现。您是否跟踪了源代码的链接,还检查了项目中的其他文件,以了解在更复杂的情况下如何使用该类?
kriegaex 2012年

不需要-这个主意很清楚,尽管您的解释有些冗长,但我还是认为它很有用。
tucuxi 2012年

对于您的评论,我的回复是冗长且大幅缩短。我还删除了示例代码片段。相反,现在有一个指向专用Git存储库的指针,其中包含完整的代码,以供将来参考。感谢您的反馈。
kriegaex 2012年

3

Set我认为,如果您确实需要使用,那么您就不走运了。

不过,我将使用通配符-如果您的情况足够灵活,可以使用aList代替a Set,那么您可以使用Collections.sort()List按需重新排序。如果List不需要太多更改订单,这应该是高性能的。


1
这样做没有太大优势-Set可以确保快速查找,插入和删除,而List则需要首先使用Collections.binarySearch来定位元素。最好坚持移除并重新插入。
tucuxi'4

我意识到这一点,这就是为什么我说“如果您的情况足够灵活”。性能要求可能过于宽松List,无法实用。
skaffman'4

1
列表中的@tucuxi二进制搜索为O(log n)。在TreeSet中查找?也是O(log n)。
凯文·布兰里恩

@Kevin,但是您必须经历实现内部进行二进制搜索的add(),remove()和contains()版本的动作。本质上,您将在内部使用排序列表来重新实现TreeSet。这就是为什么我写道删除和重新插入似乎更容易。不是因为性能,而是因为编码时间。
tucuxi'4

1
如果可以对集合进行排序而不是创建新实例,则我喜欢这个想法。
Stevko'4


1

这有助于了解您的对象将以小增量还是大改变。如果每个更改都很小,那么将数据放在一个保持排序的列表中会非常好。为此,您必须

  1. binarySearch查找元素的索引
  2. 修改元素
  3. 当元素大于其右手邻居时,将其与右手邻居交换
  4. 还是没有发生:当元素小于其左手邻居时,将其与左手邻居交换。

但是您必须确保没有人不经过“您”就可以更改元素。

编辑:也!Glazed Lists为此提供了一些支持:

http://publicobject.com/glazedlists/glazedlists-1.5.0/api/ca/odell/glazedlists/ObservableElementList.html


1

当我尝试实现类似于苹果iPhone滚轮的动态滚动窗格时,我查找了此问题。中的项目TreeSet是此类:

/**
 * Data object that contains a {@code DoubleExpression} bound to an item's
 * relative distance away from the current {@link ScrollPane#vvalueProperty()} or
 * {@link ScrollPane#hvalueProperty()}. Also contains the item index of the
 * scrollable content.
 */
private static final class ItemOffset implements Comparable<ItemOffset> {

    /**
     * Used for floor or ceiling searches into a navigable set. Used to find the
     * nearest {@code ItemOffset} to the current vValue or hValue of the scroll
     * pane using {@link NavigableSet#ceiling(Object)} or
     * {@link NavigableSet#floor(Object)}.
     */
    private static final ItemOffset ZERO = new ItemOffset(new SimpleDoubleProperty(0), -1);

    /**
     * The current offset of this item from the scroll vValue or hValue. This
     * offset is transformed into a real pixel length of the item distance from
     * the current scroll position.
     */
    private final DoubleExpression scrollOffset;

    /** The item index in the list of scrollable content. */
    private final int index;

    ItemOffset(DoubleExpression offset, int index) {
        this.scrollOffset = offset;
        this.index = index;
    }

    /** {@inheritDoc} */
    @Override
    public int compareTo(ItemOffset other) {
        double d1 = scrollOffset.get();
        double d2 = other.scrollOffset.get();

        if (d1 < d2) {
            return -1;
        }
        if (d1 > d2) {
            return 1;
        }

        // Double expression has yet to be bound
        // If we don't compare by index we will
        // have a lot of values ejected from the
        // navigable set since they will be equal.
        return Integer.compare(index, other.index);
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return index + "=" + String.format("%#.4f", scrollOffset.get());
    }
}

DoubleExpression可采取在JavaFX平台的runLater任务被束缚了一下,这就是为什么该指数包含在该包装类。

由于scrollOffset始终根据用户在滚轮上的滚动位置而变化,因此我们需要一种更新方法。通常,顺序总是相同的,因为偏移量是相对于商品索引位置的。索引永远不会改变,但是偏移量可以是负值,也可以是正值,具体取决于与当前vValue或hValue属性的相对距离ScrollPane

仅在需要时按需更新,只需遵循Tucuxi对上述答案的指导。

ItemOffset first = verticalOffsets.first();
verticalOffsets.remove(first);
verticalOffsets.add(first);

其中verticalOffsets是一个TreeSet<ItemOffset>。如果每次调用此更新代码片段时都打印出不符合规定的内容,则会看到它已更新。


-1

我认为没有现成的方法可以做到这一点。

您可以使用观察者模式,该模式在您更改元素内的值时会通知树集,然后将其删除并重新插入。

这样,您可以隐式保持列表的排序,而无需手动进行处理。.当然,此方法将需要TreeSet通过修改插入行为来扩展(在刚刚添加的项目上设置观察/通知机制)


5
我认为那行不通。如果元素的值以影响排序顺序的方式改变,则很可能您将无法在树中找到或删除它。
Michael Borgwardt'4
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.