在对象更改值时维护 TreeSet 排序

Posted

技术标签:

【中文标题】在对象更改值时维护 TreeSet 排序【英文标题】:maintaining TreeSet sort as object changes value 【发布时间】:2011-02-04 11:59:31 【问题描述】:

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

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

【问题讨论】:

这只是病态的好奇心,还是您在寻找特定的好处,比如性能或代码简单性? 我一直在寻找一种简单的非侵入性方法来维护集合的排序顺序,因为事件会更新其中的对象。更改成员资格会对集合的其他消费者产生副作用。 IMO,触发元素的使用似乎是排序集合的基本功能。 GUI 也有同样的问题,MVC 就是解决方案。与您的 TreeSet 对应的 GUI 会定期调用 update,或者如果值发生变化,模型会触发控制器(观察者模式) 架构的指法相当准确。 TreeSet 是我的 DOT 模型,它侦听 websocket jms 事件,然后通过演示者/控制器层传递模型更新以查看小部件。 【参考方案1】:

正如其他人所指出的,没有内置的方法。但是您始终可以使用您选择的构造函数对该 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() 方法。

【讨论】:

【参考方案2】:

我有一个类似的问题,找到了这个线程和 tucuxi 的答案(谢谢!),基于它我实现了我自己的UpdateableTreeSet。我的版本提供了方法

遍历这样的集合, 在循环内安排(延迟)元素更新/删除 无需创建集合的临时副本,最后 在循环结束后将所有更新/删除作为批量操作执行。

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

2012 年 8 月 7 日更新:该类在 GitHub repository 中提供,包括一个介绍性 README,其中包含示意性示例代码以及显示如何(不)更详细地使用它的单元测试。

【讨论】:

我喜欢这个想法——它只是意味着将“deferred-update”数组放在 UpdateableTreeSet 中,然后调用“do-deferred-updates”首先删除所有更新,然后重新- 插入它们。 正如我的代码示例所暗示的,这就是我所做的。这不仅仅是一个想法,它是一个真正的实现。您是否点击了源代码的链接并查看了项目中的其他文件以了解该类在更复杂的情况下是如何使用的? 不需要——这个想法很清楚,虽然你的解释有点冗长,但我认为它很有用。 我对您的评论做出了反应,即该帖子过长并大大缩短了它。我还删除了示例代码 sn-p。相反,现在有一个指向专用 Git 存储库的指针,其中包含完整代码,以供进一步参考。感谢您的反馈。【参考方案3】:

如果你真的需要使用Set,那么我想你就不走运了。

不过,我将添加一个通配符 - 如果您的情况足够灵活,可以使用 List 而不是 Set,那么您可以使用 Collections.sort() 重新排序 List一经请求。如果List 的顺序不必做太多更改,这应该是高效的。

【讨论】:

这样做并没有太大的优势 - Set 保证快速查找、插入和删除,而 List 则需要首先使用 Collections.binarySearch 来定位元素。最好还是坚持拆再插。 我意识到这一点,这就是为什么我说“如果你的情况足够灵活”。性能要求可能足够宽松,以允许List 实用。 @tucuxi 在 List 中的二进制搜索是 O(log n)。在 TreeSet 中查找?也是 O(log n)。 @Kevin 但您必须完成实现内部执行二进制搜索的 add()、remove() 和 contains() 版本的动作。本质上,您正在重新实现 TreeSet ,其中包含一个排序列表。这就是为什么我写了移除和重新插入似乎更容易的原因。不是因为性能,而是因为编码时间。 我喜欢这个想法,如果集合可以就地排序而不是创建一个新实例。【参考方案4】:

唯一的内置方式是删除和重新添加。

【讨论】:

【参考方案5】:

这有助于了解您的对象是以小增量还是大增量进行更改。如果每次更改都非常小,您最好将数据放入您保持排序的列表中。为此,您必须

    binarySearch 查找元素的索引 修改元素 当元素大于其右侧邻居时,将其与右侧邻居交换 或者如果没有发生这种情况:当元素小于其左侧邻居时,将其与左侧邻居交换。

但你必须确保没有人可以在不通过“你”的情况下更改元素。

编辑:还有! Glazed Lists 对此有一些支持:

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

【讨论】:

【参考方案6】:

我在尝试实现类似于苹果 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 总是根据用户在滚轮上的滚动位置而变化,我们需要一种更新方法。通常顺序总是相同的,因为偏移量是相对于项目索引位置的。索引永远不会改变,但偏移量可能是负数或正数,具体取决于项目与 ScrollPane 的当前 vValue 或 hValue 属性的相对距离。

仅在需要时按需更新,只需按照图库西上述答案的指导即可。

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

其中 verticalOffsetsTreeSet&lt;ItemOffset&gt;。如果您打印出 每次调用这个 update sn -p 的时候设置,你会看到它被更新了。

【讨论】:

【参考方案7】:

我认为没有开箱即用的方法。

您可以使用观察者模式,当您更改元素内的值时通知树集,然后移除并重新插入它。

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

【讨论】:

我认为这行不通。如果元素的值以影响排序顺序的方式发生变化,则很可能您将无法在树中找到它或将其删除。 @MichaelBorgwardt 没有理由不起作用,这是最佳答案的完全相同的解决方案,但具有额外的观察者模式。顺便说一句,这也是在许多不同语言的库中实现的,例如 QT c++ lib 使用观察模型的排序列表 @DamienMIRAS:不,这不是同一个解决方案,而且如前所述,它肯定不会起作用。接受的答案首先删除元素,然后更改其值,然后重新插入。这样可行。 Observer 将不起作用,因为如果您在更改值后通知它,您已经破坏了 TreeSet 的内部状态,如果您在更改值之前通知它,它还不能做任何事情。它仅在观察者最终像接受的答案那样实际更改值时才有效,但它并不是真正的观察者(并且不会与多个观察者一起使用)。 该死的,你是真的!我错过了那个“小细节”^^谢谢。它可以通过封装内部值来工作:onChange observable 通知 Set,然后从集合中删除实例,然后更新实例的字段并最终添加回集合。但我觉得这有点矫枉过正,在 github 上尝试一下可能会很有趣。

以上是关于在对象更改值时维护 TreeSet 排序的主要内容,如果未能解决你的问题,请参考以下文章

TreeSet排序

TreeSet源码解析笔记

java TreeSet的排序之定制排序

TreeSet排序相关总结

TreeSet排序相关总结

java中的排序--排序容器_TreeSet与TreeMap