mercredi 4 avril 2018

C++: Replace an element in a std::unordered_set to which we have an iterator

I have a std::unordered_set of pointers to some objects. The set has custom hash and equivalence functions, s.t. objects can be equal regarding the set even if the objects are not equal in the sense of "all members are equal".

Now I want to insert a new object. If an equivalent object already exists in the set, I want the old object to be replaced if and only if some condition on the "other" members (i.e., which are not part of the hash / equality check) of the objects is true.

If I decide to replace the object, I wonder how to do this most efficiently. I feel like the whole process should be doable with a single hashmap lookup. My best approach currently is this:

  • First, call set.insert(new_object). If that actually inserts the object, we are done. (This costs us one hashmap lookup.)
  • If not, an equivalent object is in the set, and we get an iterator to it. Check the condition based on new_object and the iterator. This doesn't invoke any hashmap operation.
  • If the condition is true, replace whatever the iterator points to by new_object. That's the tricky part. It looks like I can't do *the_iterator = new_object. The best I can currently figure out is to do set.erase(the_iterator); set.insert(new_object); - but that requires a new hashmap lookup for the insert, and could even cause rehashing. Both should not be necessary.

Below is a complete example demonstrating what I'm trying to do. It's a bit lengthy because of the custom hash / equality structs, sorry.

#include <unordered_set>

struct Foo {
    int set_value;
    int other_value;
};

struct SetEqual {
    bool operator()(Foo * lhs, Foo * rhs) const noexcept
    {
        return lhs->set_value == rhs->set_value;
    }
};
struct SetHasher {
    size_t operator()(Foo * foo) const noexcept
    {
        std::hash<int> hasher;
        return hasher(foo->set_value);
    }
};

std::unordered_set<Foo *, SetHasher, SetEqual> my_set;

void 
replace_if_lower(Foo * foo) 
{
    auto insertion_result = my_set.insert(foo);
    bool was_inserted = insertion_result.second;
    auto insertion_it = insertion_result.first;

    if (!was_inserted) {
        // Replace iff the other value is lower
        if (foo->other_value < (*insertion_it)->other_value) {
            // This is what I would like to do:
            // *insertion_it = foo;

            // This is the best I could figure out:
            my_set.erase(insertion_it);
            my_set.insert(foo); 
        }
    }
}

Does anybody see how to do this with just one set lookup? I would be fine with useing C++ 11 or 14 features. Thanks!

Aucun commentaire:

Enregistrer un commentaire