【问题标题】:Union iterator for maps?地图的联合迭代器?
【发布时间】:2011-11-10 21:45:24
【问题描述】:

[前言:std::map 这样的关联 C++ 容器有点像只有一个键列的微型数据库。 Boost 的 bimap 将其提升为一个两列并在两列中查找的表,但这只是类比的范围——没有“polymap”可以概括这个想法。]

无论如何,我想继续将地图视为数据库,现在我想知道是否有一个迭代器(或其他解决方案)可以让我对几个组成地图进行 UNION。也就是说,所有映射都具有相同的类型(或值类型和比较器,至少),我想要一个将整个集合视为一个大的多重映射的迭代器(重复键是可以的)并让我在正确的联合中遍历它顺序。

这样的事情是否存在,也许在 Boost 中?还是很容易装一个?在伪代码中:

std::map<K, M> m1, m2;
union_iterator<K, M> u(m1, m2)
for(auto it = u.begin(); it != u.end(); ++it) { /* ... */ }

例如,如果我们有:

m1 = { { 9:00, "Check in"}, { 12:00, "Break" }, { 16:00, "Check out"} };
m2 = { { 10:30, "coffee" }, { 12:15, "baked beans" }, { 15:00, "lies" } };

然后我希望迭代器产生:

9:00, "Check in"; 10:30, "coffee"; 12:00, "Break"; 12:15, "baked beans"; ...

【问题讨论】:

  • 我认为您需要完全相同类型的映射来实现这样的迭代器,因为无论如何它都必须取消对 std::pair &lt;key, value&gt; 的引用。
  • @Nicolas:如果容器有不同的分配器,我可能会没问题,至少如果 UNION 迭代器是只读的......当然是值类型(回想一下值类型 对,我没有说“映射类型”)和比较器必须同意。
  • 抱歉,我将“值类型和比较器”误读为“键类型和比较器”,我以为您打算使用相同的键和不同的值类型...编辑:好的!我认为作为“键/值对”的映射,所以我误解了。我的错。
  • 请注意,迭代器的++ 运算符或每个元素的等价预处理量必须是O(log n)n 是“多个”(映射的数量)。否则,您可以使用它在小于O(n log n) 的时间内执行排序。装配一个实际上是执行n-way 合并,这对于n=2 来说很容易,如示例中所示,否则有点繁琐。
  • 不是多张地图(每个“类别”数据一个),您能否拥有一张带有额外“类别”列的大地图?如果是这样,那么您可以使用boost::multi_index,它应该允许您遍历整个数据集。

标签: c++ map iterator


【解决方案1】:

有一个“polymap”:Boost.MultiIndex

【讨论】:

  • 我一直认为 multiindex 只是在 first 列(即键)上为您提供不同的视图。我弄错了吗?你能做一个合适的数据库作为多索引吗?
  • 一个“列”上的多个排序顺序只是一个用例。 MultiIndex 支持对任意表达式进行索引(实际上“列”的概念并不真正存在,只有定义键 wrt 元素的概念)。例如,查看bimap example,它在一对的两列上都有索引。
  • 我明白了 - 所以你可以在一组元组上拥有一个多索引,每个元素是一个表格行,并且索引提供对各个列的访问?
  • 您可以在元组类型本身上有一个 multi_index (AFAIK,它拥有它的内容),并通过用户定义的键提取器在一个键中定义元组的元素甚至多个元素。
  • 谢谢,很高兴知道!这不是我主要问题的答案,但无论如何 +1!
【解决方案2】:

mapS 复制到临时文件中,将一个附加到另一个(以防您可以修改它们)或使用vector 作为临时文件std::set_union 和自定义比较器是最简单的替代解决方案。

【讨论】:

    【解决方案3】:

    还是很容易安装一个?

    装配应该相当容易:对于 N 个基本映射,您的迭代器包含一个优先级队列,该队列由基本迭代器指向的元素的 N 个键进行优先级排序。对于取消引用,取消引用队列前面的迭代器。对于增量,在队列前面增加迭代器,如果它的增量不在末尾,则重新插入它。

    【讨论】:

    • 嗯,我得考虑一下。我看不太清楚,但此时我可能还不够清醒。谢谢!
    • 没有什么特别要理解的,基本上就是用优先队列合并排序后的序列。
    【解决方案4】:

    这很容易做到:

    template<class It>
    class union_iterator
    {
    public:
      union_iterator(It it1_begin, It it1_end, It it2_begin, It it2_end)
         : current1(it1_begin), current2(it2_begin), end1(it1_end), end2(it2_end)
         { if (it1_begin != it1_end && it2_begin != it2_end) {
             if (*it1_begin < *it2_begin) { current= &current1; }
             else { current = &current2; }
           } else if (it1_begin==it1_end) { current=&current2; }
           else { current = &current1; }
         }
      void operator++() { 
        if (current1!=end1 && current2 !=end2) { 
           if (*current1 < *current2) 
             { ++current1; current = &current1; } 
             else { ++current2; current=&current2; } 
        } else if (current1==end1 && current2 != end2) {
           ++current2;
           current = &current2;
        } else if (current1!=end1 && current2 == end2) {
           ++current1;
           current = &current1;
        }
      }
      typename std::iterator<It1>::value_type operator*() { return **current; }
    private:
      It current1;
      It current2;
      It end1;
      It end2;
      It *current;
    };
    

    但真正的问题是实现普通迭代器所需的所有剩余成员函数:-)。 Boost 有一些库可以帮助你做到这一点,但它可能仍然相当困难。

    【讨论】:

    • 如果:T 不是模板(从 std::iterator::value_type 中得出),迭代器位于向量/数组中,而您没有'不要假设它们是映射迭代器(使用*current1 &lt; *current2 而不是直接比较-&gt;first。您还检查current1==end1 是否多次,这可以通过更多嵌套ifs 来避免。您还使用current1++++current1 可能更快。实际上,我认为除了 operator
    • 如果你不访问-&gt;first,那么这也可以用于排序的std::vector`s
    • 不幸的是,我认为 ->first 是必要的,因为映射迭代器返回对。无论如何,它可能需要单独的地图和矢量版本——它确实需要为地图案例选择这对的第一个元素。
    • cplusplus.com/reference/std/utility/pairIn inequality comparisons (&lt;, &gt;), the first elements are compared first, and only if the inequality comparison is not true for them, the second elements are compared.魔术!
    • 哦,太好了。我不知道那件事。 :) 我会编辑答案。
    【解决方案5】:

    这不是您要求的迭代器,但我刚刚在标准库中找到了这个函数:

    § 25.4.5.2 set_union [set.union]

     template<class InputIterator1, class InputIterator2,
     class OutputIterator, class Compare>
     OutputIterator
     set_union(InputIterator1 first1, InputIterator1 last1,
     InputIterator2 first2, InputIterator2 last2,
     OutputIterator result, Compare comp);
    
    1. 效果:构造两个范围内元素的排序交集;也就是说,两个范围内都存在的元素集。
    2. 要求:结果范围不得与任何一个原始范围重叠。
    3. 返回:构造范围的结束。
    4. 复杂性:最多 2 * ((last1 - first1) + (last2 - first2)) - 1 次比较。
    5. 备注:如果 [first1,last1) 包含 m 个彼此等价的元素,而 [first2, last2) 包含 n 个与其等价的元素,则应从第一个元素复制前 min(m, n) 个元素范围到输出范围,按顺序。

    还有std::set_intersectionstd::set_differencestd::set_symmetric_difference

    【讨论】:

      【解决方案6】:

      这是我将如何实现 thiton 的答案:

      template <class container> class union_iterator
      {
      private:
          typedef std::pair<typename container::const_iterator, typename container::const_iterator> container_range;
          class container_range_compare
          {
          public:
              bool operator()(const container_range &lhs, const container_range &rhs) const
              {
                  return typename container::value_compare()(*lhs.first, *rhs.first);
              }
          };
      
          std::priority_queue<container_range, container_range_compare> m_range_queue;
          container::const_iterator m_current_iterator;
          bool m_is_valid;
      
          void add_container(const container &cont)
          {
              add_container_range(std::make_pair(cont.begin(), cont.end()));
          }
      
          void add_container_range(const container_range &range)
          {
              if (range.first!=range.second)
              {
                  m_range_queue.push(range);
              }
          }
      
      public:
          union_iterator(const container &a): m_valid(false)
          {
              add_container(a);
          }
      
          bool next()
          {
              m_is_valid= false;
      
              if (!m_range_queue.empty())
              {
                  container_range range= m_range_queue.pop();
                  m_current_iterator= range.first;
      
                  ++range.first;
                  add_container_range(range);
      
                  m_is_valid= true;
              }
      
              return m_is_valid;
          }
      
          typename const container::value_type &operator *() const
          {
              return *m_current_iterator;
          }
      
          typename const container::value_type *operator ->() const
          {
              return m_current_iterator.operator ->();
          }
      };
      

      它的用法与union_iterator&lt;K, V&gt; 略有不同,但它实现了基本思想。您可以扩展构造函数以接受多个适合您的映射,并在while (iterator.next()) 循环而不是for (...) 循环中使用它。

      编辑:我通过一次完成所有弹出和推送来简化了next()。所以现在更简单了! (也可以花费一些精力使它像一个 STL 迭代器,但这会变得乏味。)

      【讨论】:

      • 我还没有详细阅读这篇文章(但如果你愿意,我可能会添加另一个赏金),但这是否利用了组件映射已经排序的事实?
      • 是的;事实上,它们还没有排序是行不通的:)
      • 好的,我会再增加一个 +50 的赏金(当我下一次获得 250+ 代表时)——没有人应该没有赏金 :-)
      【解决方案7】:

      作为我announced,我得到了一些很酷的东西。

      我现在发布它,因为我不确定今晚是否能及时回来发布它。我花几句话解释一下。 (在这篇文章中)

      PS. 包含将被削减(约 20%);我可能也会对代码做一些更一般的工作。

      关于这段代码可以说很多:它不是很有效,也不是很干净(还)。然而,它几乎是无限通用的,应该像其他任何东西一样扩展。所有代码都可以在 github gist 中找到:

      • merge_maps_iterator.hpp
      • Makefile
      • test.cpp - 一组相当神秘的测试用例展示了通用性
        (我并不是说用整数和浮点数作为键的映射是一个好主意 (更不用说两者同时进行了)-只是表明可以做到)

      这是你可以找到的 test.cpp 的输出:

       == input ========================================
      { 2, aap }      { 23, mies }    { 100, noot }   { 101, broer }  
      { b, 3.14 }     
       == output =======================================
           2: aap;
          23: mies;
          98: 3.14;
         100: noot;
         101: broer;
      
       == input ========================================
      { b, 3.14 }     
      { 2, aap }      { 23, mies }    { 100, noot }   { 101, broer }  
       == output =======================================
           2: aap;
          23: mies;
          98: 3.14;
         100: noot;
         101: broer;
      
       == input ========================================
      { 2, aap }      { 23, mies }    { 100, noot }   { 101, broer }  
      { 2, aap }      { 23, mies }    { 100, noot }   { 101, broer }  
       == output =======================================
           2: aap;aap;
          23: mies;mies;
         100: noot;noot;
         101: broer;broer;
      
       == input ========================================
      { b, 3.14 }     
      { b, 3.14 }     
       == output =======================================
           b: 3.14;3.14;
      
       == input ========================================
      { 1.0, dag }    { 22.0, bye }   { 24.0, Tschüß }
      { 1, true }     { 22, false }   { 24, true }    
      { b, 3.14 }     
      { 2, aap }      { 23, mies }    { 100, noot }   { 101, broer }  
       == output =======================================
         1.0: dag;true;
         2.0: aap;
        22.0: bye;false;
        23.0: mies;
        24.0: Tschüß;true;
        98.0: 3.14;
       100.0: noot;
       101.0: broer;
      
       == input ========================================
      { 1.0, dag }    { 2.0, EXTRA }  { 22.0, bye }   { 24.0, Tschüß }
      { 1, true }     { 22, false }   { 24, true }    
      { b, 3.14 }     
      { 2, aap }      { 23, mies }    { 100, noot }   { 101, broer }  
       == output =======================================
         1.0: dag;true;
         2.0: EXTRA;aap;
        22.0: bye;false;
        23.0: mies;
        24.0: Tschüß;true;
        98.0: 3.14;
       100.0: noot;
       101.0: broer;
      

      【讨论】:

      • 谢谢 - 我得详细看看,但看起来很有希望!键相同时映射类型的排序标准是什么?
      • @KerrekSB:对不起,今晚不能发帖,我觉得我被公共汽车撞了(我发誓不只是 C++ :))我会发布背景/明天解释。感谢您的接受/赏金。非常感谢!
      • 不用担心。反正我下周才有时间看这个。 (不过,我正在努力解决我未被接受的问题:-)。)
      • 这确实很酷!在预编译所有头文件(大约需要 1 分钟)之后,这实际上变得可用了 :-) 说,有没有办法绕过 boost::fusion::tie 而只使用 std::tuplestd::tie?另外,有没有办法在没有业力的情况下打印迭代器的映射值?
      • @KerrekSB 首先,我注意到我的答案中的链接是the gist 的旧版本...哎呀。我不记得我改变了什么,但自链接版本以来已经看到了 4 个修订版(希望你看到了)
      【解决方案8】:

      使用 boost function_output_iterator 的非常简单的解决方案:

      typedef std::map< std::string, std::string > Map;
      Map first_map, second_map;
      ... // fill maps
      // iterate over maps union
      std::merge(
                  first_map.begin(), first_map.end(),
                  second_map.begin(), second_map.end(),
                  boost::make_function_output_iterator(
                      []( const Map::value_type & pair )
                      {
                          std::cout << 
                          "key = " << pair.first << 
                          "; value = " << pair.second << std::endl;
                      }       
                  ),
                  first_map.value_comp()
          );
      

      我们可以通过使用 boost::set_union(范围版本)而不是 std::set_union 使这个解决方案更漂亮。

      UPD 更新版本使用不同的键/值类型:

      typedef std::map< int, char > FirstMap;
      typedef std::map< short, std::string > SecondMap;
      FirstMap        first_map;
      SecondMap       second_map;
      
      ... // fill maps
      
      struct CustomOutput
      {
          void operator()( const FirstMap::value_type & pair ) const
          {
              std::cout << "key = " << pair.first <<
              "; value = " << pair.second << std::endl;
          }
      
          void operator()( const SecondMap::value_type & pair ) const
          {
              std::cout << "key = " << pair.first <<
              "; value = " << pair.second << std::endl;
          }
      };
      
      struct CustomPred
      {
          bool operator()( const FirstMap::value_type & first_pair, const SecondMap::value_type & second_pair ) const
          { return first_pair.first < second_pair.first; }
      
          bool operator()( const SecondMap::value_type & second_pair, const FirstMap::value_type & first_pair ) const
          { return second_pair.first < first_pair.first; }
      };
      
      // iterate over maps union
      std::merge(
                  first_map.begin(), first_map.end(),
                  second_map.begin(), second_map.end(),
                  boost::make_function_output_iterator( CustomOutput() ),
                  CustomPred()
          );
      

      UPD2 std::set_union 替换为 std::merge

      【讨论】:

      • 是的。这很简单。主要是因为它没有按照 OP 的要求做。这只是合并了两张地图。 OP 专门处理将 same key type 映射到 distinct 值类型的映射。最终结果永远不会是原始地图类型。 IE。 'merge'(map&lt;K,V1&gt;, map&lt;K,V2&gt;) -&gt; map&lt;K, tuple&lt;optional&lt;V1&gt;, optional&lt;V2&gt;)。 (我的回答甚至允许不均匀(但可比较)的键类型,并允许调用者决定如何表示值类型。)
      • 对不起,我刚刚阅读了原始问题。但是这个解决方案可以很容易地修改为不同的键/值类型支持。我会更新我的答案。
      • 最有趣的......这看起来比我预期的更通用。嗯。我会在晚饭后试一试(我的直觉说 CustomPred 应该有 4 个重载,或者一个模板化操作符)
      • 好的,只要键不重叠,您就可以在这里获得一些里程:ideone.com/RBqEnb#(我在“输入”部分添加了输出,因为它实际上不会运行ideone)。可悲的是,键的重叠正是这里的用例(匹配不同映射中的相应条目)。
      • 当您想将其推广到任何地图并在tuple&lt;optional&lt;V1&gt;, optional&lt;V2&gt;&gt; 中组合匹配元素时,您很快就会得到我发布的内容。无论如何,看起来,仅对于 2-map 情况,我本可以使用 std::set_union 来为我谋取利益。感谢您向我展示这个 - +1 立场
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-20
      • 2016-03-30
      • 1970-01-01
      • 2012-03-02
      • 1970-01-01
      • 2019-06-20
      • 2019-05-08
      相关资源
      最近更新 更多