Saving TreeSet Sorts as Object Change Values
I have an object that defines a "natural sort order" using Comparable <>. They are stored in TreeSets.
Besides deleting and re-adding an object, is there another way to update the sort when the members that are used to determine the sort order are updated?
As others have noted, there is no built-in method. But you can always subclass this TreeSet using your constructor (s) of choice and add the necessary functionality:
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; } } } From now on, you will need to call ((UpdateableTreeSet)mySet).update(anElement, aValue) to update the sort value and the sort itself. This requires that you implement an additional update() method in the data object.
I had a similar problem, I found this thread and the tucuxi answer (thanks!), Based on which I implemented my own UpdateableTreeSet . My version provides tools for
- iterations over such a set
- schedule (deferred) updating / deleting elements from the cycle
- without creating a temporary copy of the set and finally
- do all updates / deletes as a bulk operation after the completion of the cycle.
UpdateableTreeSet hides most of the complexity from the user. In addition to deferred bulk updates / paragraphs, a singleton update / delete, as shown by tucuxi, is still available in the class.
Update 2012-08-07: the class is available in a small GitHub repository , including an introductory README with a schematic code example, as well as a block of tests showing how to (not) use it in more detail.
If you really need to use Set , then you're out of luck, I think.
I'm going to use a wildcard, although if your situation is flexible enough to work with List instead of Set , you can use Collections.sort() to re-sort the List on demand. This should be done if the order of the List should not change much.
This helps to know if your objects will change in small increments or large. If each change is very small, you would very well put your data in the list that you saved. For this you need
- binarySearch to find the index of an element
- change item
- while the item is larger than its right neighbor, swap it with your right neighbor
- or if this did not happen: while the element is smaller than its left neighbor, replace it with your left neighbor.
But you must make sure that no one can change the item without going through the "you" to do this.
EDIT: Also! Glazed lists have some support for just this:
I was looking for this problem when I tried to implement a kinetic scroll bar similar to the scrolls for Apple iPhone. Elements in a TreeSet belong to this class:
/** * 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 may take some time to complete the runLater task of the JavaFX platform, so the index is included in this wrapper class.
Since scrollOffset always changes depending on the user's scroll position on the scroll wheel, we need an update method. Usually the order is always the same, since the offset is relative to the position position of the element. The index never changes, but the offset can be negative or positive depending on the relative distance of the elements from the current value of the vValue or hValue of the ScrollPane .
To update on demand only when necessary , simply follow the directions of the above Tucuxi answer.
ItemOffset first = verticalOffsets.first(); verticalOffsets.remove(first); verticalOffsets.add(first); where verticalOffsets is a TreeSet<ItemOffset> . If you print every time this update fragment is called, you will see that it is updated.
Only the built-in method is to remove and re-add.
I do not think there is a ready-made way to do this.
You can use an observer pattern that notifies trees when you change a value inside an element, then removes and reinserts it.
This way you can implicitly keep the list sorted without having to worry about it manually. Of course, this approach will have to extend the TreeSet by changing the insertion behavior (by setting the observable / notification mechanics of the item just added)