C ++:替换我们有迭代器的std :: unordered_set中的元素

时间:2018-04-04 13:05:09

标签: c++ c++11 stl c++14

我有一些指向某些对象的std::unordered_set指针。该集具有自定义散列和等价函数,s.t。即使对象在“所有成员都相等”的意义上不相等,对象也可以在集合上相等。

现在我要插入一个新对象。如果集合中已经存在等效对象,那么当且仅当对象的“其他”成员(即,不是散列/等式检查的一部分)上的某些条件为真时,才希望替换旧对象。

如果我决定更换物体,我想知道如何最有效地做到这一点。我觉得整个过程应该可以通过单个hashmap查找来完成。我目前最好的方法是:

  • 首先,致电set.insert(new_object)。如果实际插入对象,我们就完成了。 (这会花费我们一次哈希映射查找。)
  • 如果没有,则在集合中有一个等效对象,我们得到一个迭代器。根据{{​​1}}和迭代器检查条件。这不会调用任何hashmap操作。
  • 如果条件为真,则替换迭代器指向new_object的任何内容。这是棘手的部分。看起来我做不到new_object。我目前可以理解的最好的是*the_iterator = new_object - 但这需要对插入进行新的hashmap查找,甚至可能导致重新散列。两者都没有必要。

下面是一个完整的例子,展示了我正在尝试做的事情。由于自定义哈希/等式结构,它有点冗长,抱歉。

set.erase(the_iterator); set.insert(new_object);

有人看到如何通过一组查找来完成此操作吗?我可以使用C ++ 11或14功能。谢谢!

2 个答案:

答案 0 :(得分:3)

不,这是不可能的,按设计

对集合中包含的元素的所有访问都是const限定的,因此集合可以维护必要的结构,以便能够在O(1)时间内进行搜索。如果你可以修改元素,它们可能位于错误的位置。

您可以insertmy_set(或直接在C ++ 17 merge中)的内容转换为新的(单个元素)集然后交换它们,但这是一个O(N)操作。

decltype(my_set) temp{ { foo } }; 
temp.insert(my_set.begin(), my_set.end());
std::swap(my_set, temp);
// my_set now has foo, temp has replaced element or is empty

或者,如果您只关心foo->other_value放入集合的值,请注意您可以通过const指针修改对象。

auto ptr = *my_set.insert(foo).first;
ptr->other_value = foo->other_value;
assert(ptr->set_value == foo->set_value);
// ptr is "Foo * const", not "Foo const *"

答案 1 :(得分:0)

这样的事情对你有用吗?可能不是最好的设计,您可以使用成员class MySet对其进行优化(可能有一个包装器replace_if_lower)。我们的想法是使用shared_ptr为可能不安全的操作添加一点安全性。

#include <iostream>
#include <unordered_set>
#include <memory>

struct Foo
{
    int set_value;
    int other_value;
    Foo(int _s, int _o)
        :
          set_value(_s),
          other_value(_o)
    {}

    friend std::ostream& operator << (std::ostream& _s, const Foo& _f)
    {
        return _s << _f.set_value << ", "  << _f.other_value;
    }
};

using FooPtr = std::shared_ptr<Foo>;

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

std::unordered_set<FooPtr, SetHasher, SetEqual> my_set;

void replace_if_lower(FooPtr& 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;

            // Swap the pointer contents:
            const_cast<FooPtr&>(*insertion_it).swap(foo);
        }
    }
}

int main()
{
    my_set.emplace(std::make_shared<Foo>(1,1));
    my_set.emplace(std::make_shared<Foo>(2,1));
    my_set.emplace(std::make_shared<Foo>(3,1));

    std::cout << "Foo set:\n";
    for (const auto& f : my_set)
    {
        std::cout << f << ": " << *f << "\n";
    }
    std::cout << "\n";

    FooPtr f30(std::make_shared<Foo>(3,0));

    replace_if_lower(f30);

    std::cout << "Foo set:\n";
    for (const auto& f : my_set)
    {
        std::cout << f << ": " << *f << "\n";
    }
    std::cout << "\n";

    FooPtr f32(std::make_shared<Foo>(3,2));

    replace_if_lower(f32);

    std::cout << "Foo set:\n";
    for (const auto& f : my_set)
    {
        std::cout << f << ": " << *f << "\n";
    }
    std::cout << "\n";

    return 0;
}

打印

Foo set:
0x563e834eec70: 3, 1
0x563e834eec90: 2, 1
0x563e834eec30: 1, 1

Foo set:
0x563e834efd30: 3, 0
0x563e834eec90: 2, 1
0x563e834eec30: 1, 1

Foo set:
0x563e834efd30: 3, 0
0x563e834eec90: 2, 1
0x563e834eec30: 1, 1