【问题标题】:Using Both Map and List for Same Objects对同一对象同时使用 Map 和 List
【发布时间】:2015-04-07 22:28:12
【问题描述】:

我正在尝试同时使用列表和 unordered_map 来存储同一组对象。我是 C++ 新手,所以仍然对迭代器感到满意。

假设我有以下测试代码:

class Test {
public:
    int x;
    int y;
    int z;
    Test (int, int, int);
}

Test t1 = Test(1,2,3);
Test t2 = Test(2,4,6);
Test t3 = Test(3,6,9);

std::list<Test> list;
std::unordered_map<int, Test> map;

list.push_back(t3);
list.push_back(t2);
list.push_back(t1);
map[101] = t1;
map[102] = t2;
map[103] = t3;

是否可以通过key查找对象,然后从对象的引用(或者从unordered_map生成器?)生成列表迭代器?

因此,如果我有密钥 102,我可以在恒定时间内查找 t2。然后我想相对于 t2 在列表中的位置迭代向前/向后/插入/删除。

我可以使用 find 来获取指向 t2 的 unordered_map 迭代器。不知道怎么生成从t2开始的列表迭代器(我只能在列表的开头或者结尾生成迭代器,然后遍历。)

如果有人指点我有关 STL 和迭代器的优秀教程,我将不胜感激。

谢谢!

三思而后行: 这是一种可接受的方法吗?我有很多对象,需要通过整数键有效地查找它们。我还需要保留它们的顺序(与这些整数键无关)并有效地插入/删除/遍历。

【问题讨论】:

  • list和map都存储类的instance,两者都不能存储同一个实例,只是副本。如果您希望列表和地图都存储完全相同的实例,则需要使用指针。
  • 是的,这就是目的。因此,如果我使用映射查找对我的 Test 实例的引用,我如何才能找到该引用在列表中的哪个位置并获取上一个或下一个列表元素?我可以在恒定时间内从列表中删除此引用吗?
  • 在 C 中,我会实现一个双向链表,然后有一个哈希映射指向这个双向链表的元素。什么是 C++ 等价物?
  • 不,您不能在恒定时间内从列表中删除任意节点,除非您对列表中的节点有迭代器。
  • 我明白了。听起来下面的解决方案是我正在寻找的。我很惊讶这不会更频繁地出现(通常是我解决问题的迹象)。为此,我需要存储指向迭代器的指针,以便在我更改移动列表中的数据时可以更新所有“记住”迭代器位置的结构。听起来手动实现/管理双向链表更可取?

标签: c++ list c++11 iterator unordered-map


【解决方案1】:

如果你想做的是这样的:

是否可以通过key查找对象,然后从对象的引用(或者从unordered_map生成器?)生成列表迭代器?

然后您可以利用 list 迭代器在插入或擦除时不会失效(除非您擦除该特定迭代器)这一事实,并像这样重新组织您的结构:

std::list<Test> list;
std::unordered_map<int, std::list<Test>::iterator> map;

map.insert(std::make_pair(101, 
    list.insert(list.end(), t1)));
map.insert(std::make_pair(102, 
    list.insert(list.end(), t2)));
map.insert(std::make_pair(103, 
    list.insert(list.end(), t3)));

这样,您的地图查找就可以准确地为您提供所需的内容:listiterator

【讨论】:

  • 谢谢。这真的很聪明。这是在列表中存储位置的理想方式吗?我可能想在其他地图或其他数据结构的列表中标记某些点。那么,每当我在列表中插入一个元素时,我都会将此迭代器存储在不同的地方吗?感觉有点hacky。如果我使用地图查找迭代器,然后将列表中该位置的元素移动到列表中的其他位置,然后将该位置的迭代器存储到我的地图中?
  • @user2411693 理想吗?我不知道。这绝对不是 hack——如果你想存储迭代器,你必须存储迭代器。任何时候你操作列表,是的,你必须确保地图保持一致。最好的方法是将其包装在一个控制 erase()insert() 的类中。
  • 谢谢!在标记为正确之前,我会稍等片刻,看看是否有人对我有更好的方法。我倾向于在我的 Test 类中手动实现一个双向链表,因为我可以让这些查找结构直接指向对象(而不是在我移动某些东西时更新它们中的每一个或让它们存储指向迭代器的指针)。
【解决方案2】:

虽然 Barry 的方法很好,但还有另一种更先进、更复杂的方法。您可以将数据对象、(整数)键和所有簿记位放在一块内存中。因此,数据局部性将得到改善,内存分配器的压力将更小。例如,使用boost::intrusive

#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>
#include <array>

using namespace boost::intrusive;

class Foo {
    // bookkeeping bits
    list_member_hook<> list_hook;
    unordered_set_member_hook<> set_hook;

    const int key;
    // some payload...

public:
    // there is even more options to configure container types
    using list_type = list<Foo, member_hook<Foo, list_member_hook<>, &Foo::list_hook>>;
    using set_type = unordered_set<Foo, member_hook<Foo, unordered_set_member_hook<>, &Foo::set_hook>>;

    Foo(int key): key(key) {};
    bool operator ==(const Foo &rhs) const {
        return key == rhs.key;
    }
    friend std::size_t hash_value(const Foo &foo) {
        return std::hash<int>()(foo.key);
    }
};

class Bar {
    Foo::list_type list;

    std::array<Foo::set_type::bucket_type, 17> buckets;
    Foo::set_type set{Foo::set_type::bucket_traits(buckets.data(), buckets.size())};

public:
    template<typename... Args>
    Foo &emplace(Args&&... args) {
        auto foo = new Foo(std::forward<Args>(args)...);
        // no more allocations
        list.push_front(*foo);
        set.insert(*foo);
        return *foo;
    }
    void pop(const Foo &foo) {
        set.erase(foo);
        list.erase(list.iterator_to(foo));
        // Lifetime management fun...
        delete &foo;
    }
};

int main() {
    Bar bar;
    auto &foo = bar.emplace(42);
    bar.pop(foo);
}

衡量这两种算法在您的数据上的表现如何。我的想法可能只会给您带来更高的代码复杂性。

【讨论】:

    猜你喜欢
    • 2012-06-22
    • 2022-01-23
    • 2022-01-07
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多