用于映射的联合迭代器

Union iterator for maps?

本文关键字:迭代器 映射 用于      更新时间:2023-10-16

[前言:std::map这样的关联c++容器有点像只有一个键列的微型数据库。Boost的bimap将其提升为两列表,并在两列中进行查找,但这是到目前为止的类比-没有"polymap"来概括这个想法。

在任何情况下,我都想继续把映射看作数据库,我现在想知道是否有一个迭代器(或其他解决方案)允许我对几个组成映射进行UNION。也就是说,所有映射都具有相同的类型(或者至少具有相同的值类型和比较器),并且我想要一个单一的迭代器,它将整个集合视为一个大的multimap(重复键可以),并允许我以正确的联合顺序遍历它。

是否存在这样的东西,也许在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"; ...

有一个"polymap": Boost.MultiIndex.

正如我所宣布的,我有一个非常酷的东西。

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

p。包括将被削减(约20%);我可能还会对代码做一些更一般的工作。

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

  • merge_maps_iterator.hpp
  • Makefile
  • test.cpp——一组相当神秘的测试用例,展示了
    的通用性(我并不是说用int和float来设置映射键是个好主意(更不用说同时使用这两种键了)——只是表明这是可以做到的)
下面是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;

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

我将如何实现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<K, V>略有不同,但它实现了基本的思想。您可以扩展构造函数以接受多个映射,并在while (iterator.next())循环中使用它,而不是for (...)循环。

编辑:我简化了next()做所有的弹出和推一次。所以现在更简单了!(也可以花费一些精力使其像STL迭代器一样,但这会很繁琐。)

使用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 (range version)而不是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

还是很容易组装起来?

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

这是很容易做到的:

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提供了一些库来帮助您这样做,但它可能仍然相当困难。

这不是你想要的迭代器,但我只是在标准库中找到了这个函数:

§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. Effects:构造两个范围元素的排序交集;也就是说,在两个范围中都存在的元素的集合。
  2. 要求:结果范围不得与原始范围重叠。
  3. 返回:构造范围的结束。
  4. 复杂度:最多2 * ((last1 - first1) + (last2 - first2)) - 1比较。
  5. 备注:如果[first1,last1)包含m个相互等价的元素,[first2, last2)包含n个相互等价的元素,则从第一个范围依次复制最小(m, n)个元素到输出范围。

还有std::set_intersection, std::set_differencestd::set_symmetric_difference