Pauan / rust-signals

Zero-cost functional reactive Signals for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Changing MutableVec elements while affecting sorting

njam opened this issue · 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.

commented

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.