【问题标题】:is there an iterator across unique keys in a std::multimap?std::multimap 中的唯一键是否存在迭代器?
【发布时间】:2025-12-23 07:15:08
【问题描述】:

是否有一种简单或标准的方法来让多图迭代器遍历多图中的唯一键?

即对于一组看起来像:{1, "a"}, {1, "lemon"}, {2, "peacock"}, {3, "angel"} 一个从{1, "a"}开始然后递增将指向{2, "peacock"}然后再次递增将指向{3, "angel"}的迭代器?

【问题讨论】:

    标签: c++ multimap


    【解决方案1】:

    您可以使用upper_bound 代替++ 来增加迭代器位置:

    #include <map>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    int main()
    {
      multimap<int,string> mm;
      mm.insert(make_pair(1, "a"));
      mm.insert(make_pair(1, "lemon"));
      mm.insert(make_pair(2, "peacock"));
      mm.insert(make_pair(3, "angel"));
    
      for( auto it = mm.begin(), end = mm.end();
           it != end;
           it = mm.upper_bound(it->first)
      )
        cout << it->first << ' ' << it->second << endl;
      return 0;
    }
    

    这个results in

    1 a
    2 peacock
    3 angel
    

    【讨论】:

    • @ddevienne 很遗憾,现在每个人都选择优雅而不是性能。
    • 性能可能取决于数据布局。给定 N 作为键的数量和 M 作为条目的数量,平凡循环的复杂度将为 O(M),而二分搜索循环的复杂度为 O(N log M)。如果 N 则很容易证明后者更小
    【解决方案2】:

    使用upper_bound 会导致一个易于阅读的循环,但每次调用都会执行二叉树搜索,结果是 O(n log n) 而不是 O( n) 遍历。如果效率差异很重要,您可以像这样构造遍历:

    typedef std::multimap<std::string, int> MapType;
    MapType container;
    for (MapType::iterator it = container.begin(); it != container.end(); ) {
      std::string key = it->first;
    
      doSomething(key);
    
      // Advance to next non-duplicate entry.
      do {
        ++it;
      } while (it != container.end() && key == it->first);
    }
    

    【讨论】:

    【解决方案3】:

    如果您必须快速传递所有唯一键,则可以使用 std::map 代替;

    typedef std::map< KeyType, std::list< ValueType > > MapKeyToMultiValue;
    

    插入会更困难,但是您可以遍历所有键,而不必担心重复的条目。插入将如下所示:

    void insert_m(MapKeyToMultiValue &map, const KeyType key, const ValueType value )
    {
      auto it = map.find( key );
      if (it == map.end())
      {
         std::list<ValueType> empty;
         std::pair< MapKeyToMultiValue::iterator, bool > ret =
            map.insert( MapKeyToMultiValue::value_type( key, empty ) );
         it = ret.first;
      }
    
      it->second.push_back( value );
    }
    

    或者你可以把它做成模板:

    template<typename KeyType, typename ValueType, 
         typename MapType = std::map< KeyType, std::list< ValueType > > >
    void insert_multi( MapType &map, const KeyType key, const ValueType value )
    {
    
      auto it = map.find( key );
      if (it == map.end())
      {
         std::list<ValueType> empty;
         std::pair< typename MapType::iterator, bool > ret =
            map.insert( typename MapType::value_type( key, empty ) );
         it = ret.first;
      }
    
      it->second.push_back( value );
    }
    

    完整的测试程序如下:

    #include <map>
    #include <list>
    #include <string>
    #include <stdio.h>
    
    typedef std::string KeyType;  
    typedef int ValueType;
    
    typedef std::map< KeyType, std::list< ValueType > >  MapKeyToMultiValue;
    
    void insert_m(MapKeyToMultiValue &map, const KeyType key, const ValueType value )
    {
      auto it = map.find( key );
      if (it == map.end())
      {
         std::list<ValueType> empty;
         std::pair< MapKeyToMultiValue::iterator, bool > ret =
            map.insert( MapKeyToMultiValue::value_type( key, empty ) );
         it = ret.first;
      }
    
      it->second.push_back( value );
    }
    
    
    template<typename KeyType, typename ValueType, 
       typename MapType = std::map< KeyType, std::list< ValueType > > >
    void insert_multi( MapType &map, const KeyType key, const ValueType value )
    {
    
      auto it = map.find( key );
      if (it == map.end())
      {
         std::list<ValueType> empty;
         std::pair< typename MapType::iterator, bool > ret =
            map.insert( typename MapType::value_type( key, empty ) );
         it = ret.first;
      }
    
      it->second.push_back( value );
    }
    
    
    int main()
    {
        MapKeyToMultiValue map;
    
    
        insert_m(map, std::string("aaa"), 1 );
        insert_m(map, std::string("aaa"), 2 );
        insert_m(map, std::string("bb"), 3 );
        insert_m(map, std::string("cc"), 4 );
    
    
        insert_multi(map, std::string("ddd"), 1 );
        insert_multi(map, std::string("ddd"), 2 );
        insert_multi(map, std::string("ee"), 3 );
        insert_multi(map, std::string("ff"), 4 );
    
    
        for(auto i = map.begin(); i != map.end(); ++i)
        {
          printf("%s\n", i->first.c_str() );
        }
    
    
        return 0;
    }
    

    【讨论】:

    • 这太复杂了。 std::map&lt;int, std::list&lt;int&gt; &gt; map; 的插入只是 map[1].push_back(2); 。如果operator[]查找没有找到,则默认构造列表,这里很方便。
    【解决方案4】:

    可运行示例

    这是对https://*.com/a/24212648/895245 的轻微改进,带有可运行的单元测试:

    #include <cassert>
    #include <map>
    #include <vector>
    
    int main() {
    
        // For testing.
        auto m = std::multimap<int, int>{
            {1, 2},
            {1, 3},
            {2, 4}
        };
        std::vector<int> out;
    
        // The algorithm.
        auto it = m.begin();
        auto end = m.end();
        while (it != end) {
            auto key = it->first;
    
            // Do what you want to do with the keys.
            out.push_back(key);
    
            do {
                if (++it == end)
                    break;
            } while (it->first == key);
        }
    
        // Assert it worked.
        assert(out == std::vector<int>({1, 2}));
    }
    

    【讨论】:

    • 我鼓励大家避免使用 goto。在这里通过替换“goto end;”来避免它。 ->“打破;”和 "while (true)" -> "while (it != end)"
    • @bebbo 感谢您的编辑,但我认为由于缺少对key 的更新,代码将无法正常工作。请确保编译并运行它;-)
    • @bebbo 我也得睡觉 :-) 我希望有办法不进行 2 次 it == end 比较,即使这需要 goto
    • 您至少需要检查一张空地图。所以代码中总是有2个比较。
    【解决方案5】:

    如所选答案中所述,重复使用 multimap::upper_bound 会导致 O(n log n) 遍历地图。使用外部 upper_bound 函数可以得到 O(n)。但是,您需要确保只比较地图的键:

    std::multimap<int, std::string> myMap = ... ;
    const auto compareFirst = [](const std::pair<const int, std::string>& lhs, const std::pair<const int, std::string>& rhs) {
        return lhs.first < rhs.first;
    };
    
    for(auto it = myMap.begin(); it != myMap.end(); it = std::upper_bound(it, myMap.end(), *it, compareFirst)) {
        // Do stuff...
    
    }
    

    基本方法与 user3701170 的解决方案基本相同 - 即线性搜索 - 但我们将增量步骤放在 for 语句中,而不是循环体。除了将增量放在“通常”存在的位置之外,这还意味着循环中的任何 continue 语句都将按预期运行。

    【讨论】:

      【解决方案6】:

      试试equal_range:

      http://en.cppreference.com/w/cpp/container/multimap/equal_range

      那必须是完全匹配的。

      【讨论】:

      • equal_range 在条目中采用多映射键...所以如果需要检索键则不好...