【问题标题】:What multi-map-like data structure can support replacement while maintaining order?什么样的多图式数据结构可以支持替换的同时保持顺序?
【发布时间】:2015-04-27 21:33:35
【问题描述】:

我希望实现一个多映射来维护条目的插入顺序,并允许就地插入/替换而不影响顺序。 Guava 的 LinkedListMultimap 几乎是完美的,但不允许我正在寻找的替代类型。 LinkedListMultimap 实现为哈希映射和多个链表;它看起来像这样:

                             ________________
                            /                \
(A,1) -> (B,2) -> (A,3) -> (C,4) -> (B,5) -> (C,6) -> (A,7)
 \________\_______/\________________/_________________/
           \_______________________/

在内部,每个节点都有一个指向序列中下一个节点的指针,以及具有相同键的下一个节点,并且哈希表维护从键到具有该键的第一个节点的映射。

不幸的是,这不允许有效的就地插入或替换。例如,要将(C,4) 替换为(B,8),我必须向后走任意长的路才能找到(B,2),以更新其“同一个键的下一个”指针。

到目前为止,我最好的想法是将每个元素与一个序列号相关联,并为每个键保留一个排序集。但是要插入到序列的中间,我需要无限可分的序列号。

(顺便说一句,我正在用 C++ 实现它,但我只是在寻找一个可以工作的数据结构的描述。如果有一个预先存在的库可以工作,那就太好了,但即使boost::multi_index_container 似乎无法胜任这项任务。)

【问题讨论】:

    标签: data-structures language-agnostic guava boost-multi-index


    【解决方案1】:

    答案#1

    为什么 Boost.MultiIndex 在这里没有帮助您?

    Live On Coliru

    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/sequenced_index.hpp>
    #include <boost/multi_index/ordered_index.hpp>
    #include <boost/multi_index/composite_key.hpp>
    #include <boost/multi_index/member.hpp>
    
    using namespace boost::multi_index;
    
    #include <iosfwd>
    
    template<typename T,typename Q>
    struct pair_
    {
      T first;
      Q second;
    };
    
    template<typename T,typename Q>
    std::ostream& operator<<(std::ostream& os,const pair_<T,Q>& p)
    {
      return os<<"("<<p.first<<","<<p.second<<")";
    }
    
    template<typename T,typename Q>
    using list_multimap=multi_index_container<
      pair_<T,Q>,
      indexed_by<
        sequenced<>,
        ordered_non_unique<
          composite_key<
            pair_<T,Q>,
            member<pair_<T,Q>,T,&pair_<T,Q>::first>,
            member<pair_<T,Q>,Q,&pair_<T,Q>::second>
          >
        >
      >
    >;
    
    template<typename T,typename Q>
    std::ostream& operator<<(std::ostream& os,const list_multimap<T,Q>& lmm)
    {
       for(const auto& p:lmm)os<<p<<" ";
       return os;
    }
    
    #include <string>
    #include <iostream>
    
    int main()
    {
      list_multimap<std::string,int> lmm{{"A",1},{"B",2},{"A",3},{"C",4},{"B",5},{"C",6},{"A",7}};
      auto&                          mm=lmm.get<1>();
    
      std::cout<<lmm<<"\n";
    
      // List values with key "A"
    
      auto r=mm.equal_range("A");
      while(r.first!=r.second)std::cout<<*(r.first)++<<" ";
      std::cout<<"\n";
    
      // replace (C,4) with (B,8)
    
      mm.replace(mm.find(std::make_tuple("C",4)),{"B",8});
      std::cout<<lmm<<"\n";
    }
    

    【讨论】:

    【解决方案2】:

    答案 #2

    我认为,我的第一个答案可以细化以得到你所追求的:

    Live On Coliru

    #include <algorithm>
    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/random_access_index.hpp>
    #include <boost/multi_index/ordered_index.hpp>
    #include <boost/multi_index/identity.hpp>
    #include <functional>
    
    using namespace boost::multi_index;
    
    #include <iosfwd>
    
    template<typename T,typename Q>
    struct pair_
    {
      T first;
      Q second;
    
      using compare=std::function<bool(const pair_&,const pair_&)>;
      mutable compare* subcmp;
    
      pair_(const T& first,const Q& second,compare* subcmp=nullptr):
        first(first),second(second),subcmp(subcmp){}
    };
    
    namespace std{
    
    template<typename T,typename Q>
    struct less<pair_<T,Q>>
    {
      bool operator()(const pair_<T,Q>& x,const pair_<T,Q>& y)const
      {
         if(x.first<y.first)return true;
         if(y.first<x.first)return false;
         if(x.subcmp)       return (*x.subcmp)(x,y);
         if(y.subcmp)       return (*y.subcmp)(x,y);
         return false;
      }
    
      template<typename R>
      bool operator()(const R& x,const pair_<T,Q>& y)const
      {
         return x<y.first;
      }
    
      template<typename R>
      bool operator()(const pair_<T,Q>& x,const R& y)const
      {
         return x.first<y;
      }
    };
    
    } // namespace std
    
    template<typename T,typename Q>
    std::ostream& operator<<(std::ostream& os,const pair_<T,Q>& p)
    {
      return os<<"("<<p.first<<","<<p.second<<")";
    }
    
    template<typename T,typename Q>
    using list_multimap=multi_index_container<
      pair_<T,Q>,
      indexed_by<
        random_access<>,
        ordered_non_unique<identity<pair_<T,Q>>>
      >
    >;
    
    template<typename T,typename Q>
    std::ostream& operator<<(std::ostream& os,const list_multimap<T,Q>& lmm)
    {
       for(const auto& p:lmm)os<<p<<" ";
       return os;
    }
    
    #include <string>
    #include <iostream>
    
    int main()
    {
      list_multimap<std::string,int> lmm{{"A",1},{"B",2},{"A",3},{"C",4},{"B",5},{"C",6},{"A",7}};
      auto&                          mm=lmm.get<1>();
    
      std::cout<<lmm<<"\n";
    
      // list values with key "A"
    
      auto r=mm.equal_range("A");
      while(r.first!=r.second)std::cout<<*(r.first)++<<" ";
      std::cout<<"\n";
    
      // replace (C,4) with (B,8)
    
      pair_<std::string,int>::compare subcmp=[&](const auto&x, const auto& y){
        auto itx=lmm.iterator_to(x);
        auto ity=lmm.iterator_to(y);
        return itx<ity;
      };
    
      r=mm.equal_range("C");
      auto it=std::find_if(r.first,r.second,[](const auto& x){return x.second==4;});
      mm.modify(it,[&](auto&x){x={"B",8,&subcmp};});
      it->subcmp=nullptr;
      std::cout<<lmm<<"\n";
    
      // list values with key "B"
    
      r=mm.equal_range("B");
      while(r.first!=r.second)std::cout<<*(r.first)++<<" ";
      std::cout<<"\n";  
    }
    

    主要思想是:

    • 使用随机访问索引而不是排序索引。
    • 通过用户提供的比较函数对元素进行子排序(当键相等时),存储在subcmp 中,这是可选的(如果subcmp 为空)。
    • 当替换值时,使用modify(以便在适当的位置更改元素)并提供一个子比较器,它仅尊重随机访问索引中元素的顺序。修改完成后,将subcmp设置为nullptr,因为不再需要了。

    【讨论】:

      【解决方案3】:

      答案 #3

      我的第二个答案可以进一步细化,将子比较器放在 less&lt;pair_&lt;T,Q&gt;&gt; 对象本身中:

      Live On Coliru

      #include <algorithm>
      #include <boost/multi_index_container.hpp>
      #include <boost/multi_index/random_access_index.hpp>
      #include <boost/multi_index/ordered_index.hpp>
      #include <boost/multi_index/identity.hpp>
      #include <functional>
      
      using namespace boost::multi_index;
      
      #include <iosfwd>
      
      template<typename T,typename Q>
      struct pair_
      {
        T first;
        Q second;
      };
      
      namespace std{
      
      template<typename T,typename Q>
      struct less<pair_<T,Q>>
      {
        using subcompare=std::function<bool(const pair_<T,Q>&,const pair_<T,Q>&)>;
        subcompare subcmp;
      
        bool operator()(const pair_<T,Q>& x,const pair_<T,Q>& y)const
        {
           if(x.first<y.first)return true;
           if(y.first<x.first)return false;
           if(subcmp)         return subcmp(x,y);
           return false;
        }
      
        template<typename R>
        bool operator()(const R& x,const pair_<T,Q>& y)const
        {
           return x<y.first;
        }
      
        template<typename R>
        bool operator()(const pair_<T,Q>& x,const R& y)const
        {
           return x.first<y;
        }
      };
      
      } // namespace std
      
      template<typename T,typename Q>
      std::ostream& operator<<(std::ostream& os,const pair_<T,Q>& p)
      {
        return os<<"("<<p.first<<","<<p.second<<")";
      }
      
      template<typename T,typename Q>
      using list_multimap=multi_index_container<
        pair_<T,Q>,
        indexed_by<
          random_access<>,
          ordered_non_unique<
            identity<pair_<T,Q>>,
            std::reference_wrapper<const std::less<pair_<T,Q>>>>
        >
      >;
      
      template<typename T,typename Q>
      std::ostream& operator<<(std::ostream& os,const list_multimap<T,Q>& lmm)
      {
         for(const auto& p:lmm)os<<p<<" ";
         return os;
      }
      
      #include <string>
      #include <iostream>
      
      int main()
      {
        std::less<pair_<std::string,int>> less;
        list_multimap<std::string,int>    lmm{boost::make_tuple(
                                            boost::make_tuple(),
                                            boost::make_tuple(
                                              identity<pair_<std::string,int>>{},
                                              std::cref(less)
                                            )
                                          )};
        auto&                             mm=lmm.get<1>();
      
        lmm={{"A",1},{"B",2},{"A",3},{"C",4},{"B",5},{"C",6},{"A",7}};
        std::cout<<lmm<<"\n";
      
        // list values with key "A"
      
        auto r=mm.equal_range("A");
        std::for_each(r.first,r.second,[](const auto& x){std::cout<<x<<" ";});
        std::cout<<"\n";
      
        // replace (C,4) with (B,8)
      
        std::less<pair_<std::string,int>>::subcompare subcmp=
        [&](const auto&x, const auto& y){
          return lmm.iterator_to(x)<lmm.iterator_to(y);
        };
      
        r=mm.equal_range("C");
        auto it=std::find_if(r.first,r.second,[](const auto& x){return x.second==4;});
        less.subcmp=subcmp;
        mm.modify(it,[](auto& x){x={"B",8};});
        less.subcmp=nullptr;
        std::cout<<lmm<<"\n";
      
        // list values with key "B"
      
        r=mm.equal_range("B");
        std::for_each(r.first,r.second,[](const auto& x){std::cout<<x<<" ";});
        std::cout<<"\n";  
      }
      

      这使我们显着减少了内存使用,因为元素本身不需要带有指向subcmp 的额外指针。总体策略保持不变。

      【讨论】:

      • 我看到你做了什么,非常好!可悲的是,插入random_access 索引中间的是O(n),但由于它不涉及副本,因此在我的情况下可能很快。我很好奇它是否可以在亚线性时间内完成。
      • (Off Boost.MultiIndex realm.) 要进行次线性替换,您需要知道,给定两个元素 xy,它们在序列中排在第一位。类似向量的序列可以在恒定时间内为您提供,但正如您所指出的,中间插入是 O(n)。另一种方法是使用顺序统计树 (en.wikipedia.org/wiki/Order_statistic_tree) 作为基本序列:相对顺序检查可以在 O(log n) 中完成,也可以在中间插入中完成。
      • 啊,订单统计树太完美了!不敢相信我自己真的没有想到。如果您将其作为答案,我将很乐意接受。
      • 您必须进行测量,但我敢打赌,在我的示例中使用 Boost.MultiIndex 进行的类向量序列将在实践中击败顺序统计树。如果你做这个练习,请告诉我。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-11
      • 1970-01-01
      • 1970-01-01
      • 2021-01-05
      • 2016-05-14
      相关资源
      最近更新 更多