如何使用具有unordered_set的队列作为基础容器

How should I use a queue with an unordered_set as the underlying container

提问人:Alexander Soare 提问时间:3/15/2022 更新时间:3/15/2022 访问量:362

问:

我有一个包含一组 .对于此数据结构,我需要两个重要属性:std::pair<int, int>

  • 我可以快速检查设置成员资格。
  • 先进先出

因此,作为一名拥有 cppreference.com 的C++初学者,我选择了我所拥有的,并在下面的附录中包括了 的实现。std::queue<std::unordered_set<PointUV, pointUVHash>> frontierPointsUV{};typedef std::pair<int, int> PointUV;PointUVHash

我的问题是

  1. 这是满足上述 2 个要求的明智方法吗?
  2. 如何查看集合成员资格?我尝试了设置成员资格,但收到“没有成员”错误。frontierPointsUV.c.contains
  3. 如何推和弹出(或插入和擦除)?我尝试在任何一个容器上调用这些修饰符都没有成功。

附录 - 哈希实现

struct pointUVHash final
{
  // This is a safe hashing function for pointUV because we only ever expect it to have 0 or positive ints
  // in ranges well under 2**16
  int operator()(const PointUV &p) const
  {
    return int(p.first) + (int(p.second) << 16);
  }
};
C++ STL C++-标准库

评论

0赞 NathanOliver 3/15/2022
std::queue要求用于存储的容器是序列容器,该容器不是其成员。std::unordered_set
0赞 Pete Becker 3/15/2022
似乎是矛盾的:队列有顺序,而无序集合没有。你可以添加一堆代码来使其工作,但这种不匹配通常可以通过设计更改更好地解决。
2赞 Stephan Lechner 3/15/2022
@Drew Dormann:注意迭代器失效...
1赞 Stephan Lechner 3/15/2022
@Drew Dormann:insert、emplace、emplace_hint、operator[] 可能会导致重新哈希,并且重新哈希使迭代器失效(en.cppreference.com/w/cpp/container/unordered_map,迭代器失效)
1赞 Drew Dormann 3/15/2022
谢谢你们俩。<多喝几口咖啡> 删除太旧而无法编辑的坏主意评论。

答:

2赞 Homer512 3/15/2022 #1

队列适配器需要有序的容器(如 deque 或 list)才能工作。在我看来,它也已经过时了,因为它只从底层容器中删除了功能,并没有添加任何实质内容。

您想要的是两个保持同步的数据结构,例如 one 和 one 。看到如何是一个非常简单的类型,这种组合效果最好。如果开始处理更复杂的类型,并且可能希望避免重复的对象和查找,则可以选择将指针存储在其中一个容器中。unordered_set<pair<int, int> >deque<pair<int, int> >pair<int, int>

无论如何,这样的事情应该可以解决问题:

class unique_queue
{
public:
    using value_type = std::pair<int, int>;
private:
    struct hash: private std::hash<int>
    {
        std::size_t operator()(const value_type& value) const noexcept
        {
          if(sizeof(int) >= sizeof(std::size_t)) {
              const std::hash<int>& inthash = *this;
              return inthash(value.first) * 31 + inthash(value.second);
          }
          return static_cast<std::size_t>(value.first)
                << ((sizeof(std::size_t) - sizeof(int)) * CHAR_BIT)
                + static_cast<std::size_t>(value.second);
        }
    };
    using set_type = std::unordered_set<value_type, hash>;
    using queue_type = std::deque<value_type>;

    set_type set;
    queue_type queue;
public:
    bool push(const value_type& value)
    {
        std::pair<set_type::iterator, bool> inserted = set.insert(value);
        if(! inserted.second) // equal value already contained
            return false;
        try {
            queue.push_back(value);
        } catch(...) { // provide exception safety
            set.erase(inserted.first);
            throw;
        }
        return true;
    }
    bool empty() const noexcept
    { return queue.empty(); }

    const value_type& front() const noexcept
    { return queue.front(); }

    void pop() noexcept
    {
        set.erase(front());
        queue.pop_front();
    }
    bool contained(const value_type& value) const noexcept
    { return set.count(value) != 0; }
};

我使函数语义在某种程度上接近队列或 deque 提供的内容,例如,如果在空队列上调用,则未定义的行为。front()

如果希望队列中有多个相等的键,最简单的方法是替换为以跟踪队列中有多少个相等的键。像这样的东西:unordered_set<pair<int, int> >unordered_map<pair<int, int>, size_t>

class set_queue
{
public:
    using value_type = std::pair<int, int>;
private:
    struct hash: private std::hash<int>
    {
        std::size_t operator()(const value_type& value) const noexcept
        {
          if(sizeof(int) >= sizeof(std::size_t)) {
              const std::hash<int>& inthash = *this;
              return inthash(value.first) * 31 + inthash(value.second);
          }
          return static_cast<std::size_t>(value.first)
                << ((sizeof(std::size_t) - sizeof(int)) * CHAR_BIT)
                + static_cast<std::size_t>(value.second);
        }
    };
    using map_type = std::unordered_map<value_type, std::size_t, hash>;
    using queue_type = std::deque<value_type>;

    map_type map;
    queue_type queue;
public:
    bool push(const value_type& value)
    {
        std::pair<map_type::iterator, bool> inserted = map.emplace(value, 0);
        try {
            queue.push_back(value);
        } catch(...) { // provide exception safety
            if(inserted.second)
                map.erase(inserted.first);
            throw;
        }
        inserted.first->second += 1;
        return true;
    }
    bool empty() const noexcept
    { return queue.empty(); }

    const value_type& front() const noexcept
    { return queue.front(); }

    void pop() noexcept
    {
        map_type::iterator refcount = map.find(front());
        assert(refcount != map.end());
        queue.pop_front();
        refcount->second -= 1;
        if(! refcount->second) // last element of same value
            map.erase(refcount);
    }
    std::size_t count(const value_type& value) const noexcept
    {
        map_type::const_iterator found = map.find(value);
        return found == map.end() ? 0 : found->second;
    }
};

评论

0赞 user4581301 3/16/2022
关于队列过时的说明:访问限制使队列有用......当您需要队列时。如果这些限制没有用,您可能不需要队列。
0赞 Homer512 3/16/2022
@user4581301,然后你用队列编写程序,直到你添加一个 queue.clear() 有用的地方。然后,使用 deque 的方法名称略有不同来重写所有内容。耸耸肩。无论什么漂浮着你的船
0赞 user4581301 3/16/2022
这说明您不需要排队。你只是不知道,直到为时已晚。