【问题标题】:Finding a nonexisting key in a std::map在 std::map 中查找不存在的键
【发布时间】:2010-12-21 01:58:44
【问题描述】:

有没有办法在地图中找到不存在的键?

我正在使用std::map<int,myclass>,我想为新项目自动生成一个密钥。项目可能会以与插入不同的顺序从地图中删除。

myclass 项目可能相同,也可能不同,因此它们不能单独作为键。

在程序运行期间,生成和删除的item数量没有限制,所以我不能用counter作为key。

具有相同功能和性能的替代数据结构也可以。

编辑

我试图为我的项目构建一个容器 - 这样我就可以根据它们的键删除/修改项目,并且我可以迭代这些项目。键值本身对我没有任何意义,但是,其他对象将存储这些键以供内部使用。

我不能使用增量计数器的原因是,在程序的生命周期内,它们可能超过 2^32(或理论上 2^64)个项目,但是即使在所有其他项目之后,项目 0 理论上可能仍然存在项目被删除。

最好向 std::map 询问最低值的未使用键,这样我就可以将它用于新项目,而不是使用向量或其他一些外部存储来存储未使用的键。

【问题讨论】:

  • 您打算如何访问地图中的项目?听起来您甚至可能不需要地图数据结构。您是否要生成密钥然后保留它们以便访问元素?而且,如果您无论如何都必须保留密钥……那么您不知道哪些密钥正在使用,哪些未使用?
  • 我真诚地怀疑会有超过 2^64 个项目。
  • 如果您只使用 64 位计数器变量就可以了。考虑一下:在我的计算机上,仅 增加 变量 1,000,000,000 次大约需要 2 秒。这意味着,我需要至少 1169 年才能遍历 64 位整数的整个范围。现在,如果人类文明在公元 3178 年仍然存在,并且您的程序仍在运行,那么您的(可能是电子人)后代将只需要重新启动应用程序。
  • '最好向 std::map 询问最低值的未使用键':std:map 没有这样的功能。您也许可以手动实现一个红黑树(std::map 的常用数据结构),并且能够在 log(n) 时间内无需额外存储来回答您的问题
  • @Managu:这就是我的想法……但是重写整个事情是一种耻辱。也许可以继承 std::map 并添加这样的功能。

标签: c++ data-structures stl


【解决方案1】:

我建议将计数器和队列结合使用。从地图中删除项目时,将其键添加到队列中。然后,队列会跟踪已从映射中删除的键,以便再次使用它们。要获取新密钥,首先检查队列是否为空。如果不是,则弹出顶部索引并使用它,否则使用计数器获取下一个可用键。

【讨论】:

  • 如题,不能使用计数器。
  • OP 相当令人困惑的问题让我们考虑尝试生成唯一键,然后跟踪这些键。但实际上,在我看来,OP 的问题基本上归结为这样一个事实,即他需要一个能够处理重复值的数据结构。那么为什么不直接使用 std::multiset 呢?
  • @RocketSurgeon:他说的是不能使用计数器而不重用值。
【解决方案2】:

让我看看我是否明白。你想做的是

寻找钥匙。 如果不存在,则插入一个元素。

项目可能会被删除。

保留一个计数器(等待等待)和一个向量。该向量将保留已删除项目的 ID。 当您要插入新元素时,请在向量中查找键。如果向量不为空,则删除密钥并使用它。如果它是空的,从柜台(counter++)拿一个。 但是,如果您从不从地图中移除项目,那么您只会遇到计数器。

替代方案: 使用元素的内存地址作为键怎么样?

【讨论】:

  • 赞成,因为我认为您的替代方案非常符合要求。
【解决方案3】:

有没有办法找到一个不存在的 输入地图?

我不确定你在这里的意思。你怎么能找到不存在的东西?你的意思是,有没有办法判断地图是否不包含键?

如果这就是您的意思,您只需使用find 函数,如果键不存在,它将返回一个指向end() 的迭代器。

if (my_map.find(555) == my_map.end()) { /* do something */ }

你接着说……

我正在使用 std::map,并且 我想自动生成密钥 对于新项目。项目可能会被删除 从地图以不同的顺序从 他们的插入。 myclass 项目可能相同,也可能不同,因此它们不能单独作为密钥。

我有点不清楚你想在这里完成什么。您的问题似乎是您想将 myclass 的实例存储在地图中,但由于您可能有 myclass 的重复值,因此您需要某种方法来生成唯一键。与其这样做,不如直接使用std::multiset<myclass> 并存储重复项?当您查找myclass 的特定值时,多重集将返回一个迭代器,指向具有该值的myclass 的所有实例。您只需要为myclass 实现一个比较函子。

【讨论】:

    【解决方案4】:

    我想说,对于一般情况,当 key 可以具有 map 允许的任何类型时,这是不可能的。甚至能够判断是否存在一些未使用的键也需要一些关于类型的知识。

    如果我们考虑 int 的情况,您可以存储 std::set 未使用键的连续段(因为这些段不重叠,可以使用自然排序 - 只需比较它们的起点)。当需要一个新键时,你取第一个段,切断第一个索引并将其余部分放入集合中(如果其余部分不为空)。当某个键被释放时,您会发现集合中是否存在相邻段(由于集合性质,可能具有 O(log n) 复杂度)并在需要时执行合并,否则只需将 [n,n] 段放入集合中。

    通过这种方式,您肯定会拥有与 map 独立于请求历史记录相同的时间复杂度和内存消耗顺序(因为段数不能超过 map.size()+1)

    类似这样的:

    class TKeyManager
    {
    public:
        TKeyManager()
        {
            FreeKeys.insert(
              std::make_pair(
                std::numeric_limits<int>::min(),
                std::numeric_limits<int>::max());
        }
        int AlocateKey()
        {
            if(FreeKeys.empty())
                throw something bad;
            const std::pair<int,int> freeSegment=*FreeKeys.begin();
            if(freeSegment.second>freeSegment.first)
                FreeKeys.insert(std::make_pair(freeSegment.first+1,freeSegment.second));
            return freeSegment.first;
        }
        void ReleaseKey(int key)
        {
            std:set<std::pair<int,int>>::iterator position=FreeKeys.insert(std::make_pair(key,key)).first;
            if(position!=FreeKeys.begin())
            {//try to merge with left neighbour
                std::set<std::pair<int,int>>::iterator left=position;
                --left;
                if(left->second+1==key)
                {
                    left->second=key;
                    FreeKeys.erase(position);
                    position=left;
                }
            }
            if(position!=--FreeKeys.end())
            {//try to merge with right neighbour
                std::set<std::pair<int,int>>::iterator right=position;
                ++right;
                if(right->first==key+1)
                {
                    position->second=right->second;
                    FreeKeys.erase(right);
                }
            }
        }
    private:
        std::set<std::pair<int,int>> FreeKeys;
    };
    

    【讨论】:

      【解决方案5】:

      您能否解释一下为什么不能使用简单的增量计数器作为自动生成的密钥? (插入增量)?这样做似乎没有问题。

      【讨论】:

      • 因为这不涉及最终的密钥重用。
      【解决方案6】:
      • 考虑一下,您决定如何生成非基于计数器的密钥,并发现批量生成它们比逐个生成它们更有效。
      • 证明这个生成器是“无限的”和“有状态的”(这是您的要求),您可以创建第二个固定大小的容器,其中包含 1000 个未使用的键。
      • 使用此容器中的密钥为地图中的新条目提供新条目,并将密钥返回以供回收利用。
      • 设置一些较低的“阈值”以在密钥容器达到低水平时做出反应,并使用“无限”生成器批量重新填充密钥。

      实际贴出的问题依然存在“如何基于非计数器制作高效的生成器”。您可能想再看一下“无限”要求,并检查 64 位或 128 位计数器是否仍然可以在有限的一段时间内(例如 1000 年)满足您的算法。

      【讨论】:

        【解决方案7】:

        使用uint64_t作为序列的关键类型或者即使你认为这还不够

        struct sequence_key_t {
            uint64_t upper; 
            uint64_t lower; 
            operator++();
            bool operator<() 
        };
        

        喜欢:

        sequence_key_t global_counter;
        
        std::map<sequence_key_t,myclass> my_map;
        
        my_map.insert(std::make_pair(++global_counter, myclass()));
        

        你不会有任何问题。

        【讨论】:

          【解决方案8】:

          和其他人一样,我很难弄清楚你到底想要什么。如果找不到,听起来您想创建一个项目。 sdt::map::operator[] ( const key_type& x ) 将为您执行此操作。

          std::map<int, myclass> Map;
          myclass instance1, instance2;
          
          Map[instance1] = 5;
          Map[instance2] = 6;
          

          这是你的想法吗?

          【讨论】:

            【解决方案9】:

            与其他答案一起,我建议使用一个简单的计数器来生成 ID。如果您担心完全正确,您可以为计数器使用任意精度整数,而不是内置类型。或类似以下的内容,它将遍历所有可能的字符串。

            void string_increment(std::string& counter)
            {
                bool carry=true;
                for (size_t i=0;i<counter.size();++i)
                {
                    unsigned char original=static_cast<unsigned char>(counter[i]);
                    if (carry)
                    {
                        ++counter[i];
                    }
                    if (original>static_cast<unsigned char>(counter[i]))
                    {
                        carry=true;
                    }
                    else
                    {
                        carry=false;
                    }
                }
                if (carry)
                {
                    counter.push_back(0);
                }
            }
            

            例如这样你就有了:

            std::string counter; // empty string
            string_increment(counter); // now counter=="\x00"
            string_increment(counter); // now counter=="\x01"
            ...
            string_increment(counter); // now counter=="\xFF"
            string_increment(counter); // now counter=="\x00\x00"
            string_increment(counter); // now counter=="\x01\x00"
            ...
            string_increment(counter); // now counter=="\xFF\x00"
            string_increment(counter); // now counter=="\x00\x01"
            string_increment(counter); // now counter=="\x01\x01"
            ...
            string_increment(counter); // now counter=="\xFF\xFF"
            string_increment(counter); // now counter=="\x00\x00\x00"
            string_increment(counter); // now counter=="\x01\x00\x00"
            // etc..
            

            【讨论】:

              【解决方案10】:

              另一种选择,如果映射中的实际工作集足够小,将使用递增键,然后在计数器即将换行时重新生成键。该解决方案只需要临时的额外存储空间。哈希表性能将保持不变,密钥生成将只是一个 if 和一个增量。

              当前工作集中的项目数量将真正决定这种方法是否可行。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2010-12-28
                • 2018-04-21
                • 1970-01-01
                • 1970-01-01
                • 2014-10-18
                • 1970-01-01
                • 2010-09-10
                相关资源
                最近更新 更多