Changing MutableVec elements while affecting sorting
njam opened this issue · 2 comments
Another question about MutableVec sorting. Glad to get your opinion.
In my use case I would like to sort a vector of elements based on an element's attribute. This attribute can change, in which case the sort order of the elements should change.
Let's say I have a MutableVec
with three elements, and each element is itself mutable, for example a Mutable<String>
:
let mutable_vec = MutableVec::new_with_values(vec![
Mutable::new("X".to_string()),
Mutable::new("Y".to_string()),
Mutable::new("Z".to_string()),
]);
I sort this vector alphabetically:
let signal_vec = mutable_vec
.signal_vec_cloned()
.sort_by_cloned(move |left: &Mutable<String>, right: &Mutable<String>| {
left.get_cloned().cmp(&right.get_cloned())
});
Now I would like to update the last element from value "Z" to value "A", which would change its position in the sorted vector from index 2 to index 0.
As far as I can see this use case is currently not supported and I'm experiencing two problems.
I cannot notify the MutableVec
that an item has changed. As a workaround I can remove the item and re-add it to the vector.
let last_item = mutable_vec.lock_mut().remove(2);
last_item.set("A".to_string());
mutable_vec.lock_mut().push_cloned(last_item);
The sort algorithm fails if an item's ordering changes and is then removed.
When I push the modified element back to the MutableVec
the function binary_search_remove()
panics with "Could not find value: 0". I think the problem is that the algorithm doesn't expect the sort ordering of an existing element to change.
As a workaround I can create a completely new item to replace the existing one with.
What do you think of this use case? Is it valid or not? If it's valid what would be a good solution?
See PR #16 for a full test case of this scenario.
You can use map_signal
for this:
let signal_vec = mutable_vec
.signal_vec_cloned()
.map_signal(|x| x.signal_cloned())
.sort_by_cloned(move |left: &String, right: &String| {
left.cmp(&right)
});
As a general rule, if you're using get
then it won't update when it changes (it's a one-time thing), so you need to use Signal
or SignalVec
if you want it to update.
I think the problem is that the algorithm doesn't expect the sort ordering of an existing element to change.
That's true for all languages (including Rust). When you do a binary search for an element, it has to compare it to existing elements, and it has to assume that the existing elements are sorted correctly. It's not possible to fix that, it's just fundamentally how sorting works.
For example, the documentation for BTreeMap says this:
It is a logic error for a key to be modified in such a way that the key's ordering relative to any other key, as determined by the Ord trait, changes while it is in the map. This is normally only possible through Cell, RefCell, global state, I/O, or unsafe code.
And here is what the documentation for a Python BTree library says:
Another potential problem is mutability: when a key is inserted in a BTree- based structure, it must retain the same order relative to the other keys over time. This is easy to run afoul of if you use mutable objects as keys.
Key lookup relies on that the keys remain in sorted order (an efficient form of binary search is used). By mutating key L2 after inserting it, we destroyed the invariant that the OOSet is sorted. As a result, all future operations on this set are unpredictable.
I will update the documentation to mention this requirement.
Ah, great, it works perfectly! Thanks for the explanations!
I wasn't aware of SignalVecExt::map_signal
, I should read the docs more thoroughly.