【问题标题】:Chaining of ordering predicates (e.g. for std::sort)排序谓词的链接(例如,对于 std::sort)
【发布时间】:2023-03-22 04:09:02
【问题描述】:

您可以将函数指针、函数对象(或 boost lambda)传递给 std::sort 以定义要排序的容器元素的严格弱排序。

但是,有时(我已经多次提到这一点),您希望能够链接“原始”比较。

一个简单的例子是,如果您对代表联系人数据的对象集合进行排序。有时你会想要排序

姓、名、区号
。其他时候
first name, last name
- 还有其他时候
age, first name, area code
... 等等

现在,您当然可以为每种情况编写一个额外的函数对象,但这违反了 DRY 原则——尤其是在每次比较不那么琐碎的情况下。

您似乎应该能够编写比较函数的层次结构 - 低级函数执行单一的、原始的比较(例如名字

这种方法的问题在于 std::sort 采用二元谓词——谓词只能返回一个布尔值。因此,如果您正在编写它们,则无法判断“false”是否表示相等或大于。您可以让较低级别的谓词返回一个具有三种状态的 int - 但是您必须将它们包装在较高级别的谓词中,然后才能单独与 std::sort 一起使用。

总而言之,这些都不是无法克服的问题。这似乎比它应该做的更难 - 而且肯定会邀请帮助库实现。

因此,是否有人知道任何可以在这里提供帮助的预先存在的库(尤其是 std 或 boost 库) - 对此事有任何其他想法?

[更新]

正如在一些 cmets 中提到的 - 我已经着手编写自己的类实现来管理它。它相当小,一般来说可能有一些问题。但在此基础上,任何有兴趣的人都可以在这里上课:

http://pastebin.com/f52a85e4f

还有一些辅助函数(避免需要指定模板参数)在这里:

http://pastebin.com/fa03d66e

【问题讨论】:

    标签: c++ stl sorting compare predicate


    【解决方案1】:

    处理此问题的一种常规方法是多次排序并使用稳定排序。请注意,std::sort 通常不稳定稳定。但是,有std::stable_sort

    也就是说,我会为返回三态(表示小于、等于、大于)的函子编写一个包装器。

    【讨论】:

    • 你的意思是后续的传球只会对相等的范围进行排序?这也可以工作,但似乎做了额外的工作(最小,但可能很重要),并且仍然涉及一些样板。我也希望避免依赖 stable_sort,尽管不是出于任何特定原因,除了选项 :-)
    【解决方案2】:

    std::sort 不能保证是稳定的,因为稳定的排序通常比不稳定的排序慢……所以多次使用稳定的排序看起来会导致性能问题……

    是的,要求谓词真的很可惜: 除了创建一个接受三态函数向量的仿函数外,我没有其他办法......

    【讨论】:

    • 是的,事实上这正是我现在所做的。我正在使用这个类:pastebin.com/f52a85e4f ... 以及这些语法糖的辅助函数:pastebin.com/fa03d66e - 显然我可以增加 3 个函数的限制 - 但你明白了。
    【解决方案3】:

    你可以像这样构建一个小链接系统:

    struct Type {
      string first, last;
      int age;
    };
    
    struct CmpFirst {
      bool operator () (const Type& lhs, const Type& rhs) { return lhs.first < rhs.first; }
    };
    
    struct CmpLast {
      bool operator () (const Type& lhs, const Type& rhs) { return lhs.last < rhs.last; }
    };
    
    struct CmpAge {
      bool operator () (const Type& lhs, const Type& rhs) { return lhs.age < rhs.age; }
    };
    
    template <typename First, typename Second>
    struct Chain {
      Chain(const First& f_, const Second& s_): f(f_), s(s_) {}
    
      bool operator () (const Type& lhs, const Type& rhs) {
        if(f(lhs, rhs))
          return true;
        if(f(rhs, lhs))
          return false;
    
        return s(lhs, rhs);
      }
    
      template <typename Next>
      Chain <Chain, Next> chain(const Next& next) const {
         return Chain <Chain, Next> (*this, next);
      }
    
      First f;
      Second s;
    };
    
    struct False { bool operator() (const Type& lhs, const Type& rhs) { return false; } };
    
    template <typename Op>
    Chain <False, Op> make_chain(const Op& op) { return Chain <False, Op> (False(), op); }
    

    然后使用它:

    vector <Type> v;  // fill this baby up
    
    sort(v.begin(), v.end(), make_chain(CmpLast()).chain(CmpFirst()).chain(CmpAge()));
    

    最后一行有点冗长,但我认为它的意图很清楚。

    【讨论】:

    • 这个实现的问题是你的低级比较函数返回布尔值。如果当前测试比较 equal,您只想链接到下一个比较。
    • 查看我自己的尝试,我在上面对 siukurnin 的回复中发布了一个链接。我现在可以做:std::sort(c.begin(), c.end(), MakeCompareChain(f1, f2, f3));
    • 不,它确实有效——我们只需要进行两次比较(a == b 与 not(a
    • 啊,你是对的——对不起。在发表评论之前,我真的应该更仔细地阅读答案:-)。现在投票给你。
    • 看看这篇(古老的)CUJ文章:“使用类型列表创建灵活的复合排序对象)ddj.com/cpp/184401667
    【解决方案4】:

    链接解决方案很冗长。您还可以将 boost::bind 与 std::logical_and 结合使用来构建您的排序谓词。有关详细信息,请参阅链接文章:How the boost bind library can improve your C++ programs

    【讨论】:

    • 你好 MP24。你看到我自己的例子了吗:我正在使用这个类:pastebin.com/f52a85e4f ... 以及这些语法糖的辅助函数:pastebin.com/fa03d66e。我现在可以做:std::sort(c.begin(), c.end(), MakeCompareChain(f1, f2, f3));
    • 事实上,再看一遍,我毕竟没有在其中使用 boost::bind。我可能会在客户端代码中使用它——但看看我在这里所做的事情,到目前为止我还不需要它。不过,我确实倾向于在任何地方使用 boost:bind ;-)
    【解决方案5】:

    你可以试试这个:

    用法:

    struct Citizen {
        std::wstring iFirstName;
        std::wstring iLastName;
    };
    
    ChainComparer<Citizen> cmp;
    cmp.Chain<std::less>( boost::bind( &Citizen::iLastName, _1 ) );
    cmp.Chain<std::less>( boost::bind( &Citizen::iFirstName, _1 ) );
    
    std::vector<Citizen> vec;
    std::sort( vec.begin(), vec.end(), cmp );
    

    实施:

    template <typename T>
    class ChainComparer {
    public:
    
        typedef boost::function<bool(const T&, const T&)> TComparator;
        typedef TComparator EqualComparator;
        typedef TComparator CustomComparator;
    
        template <template <typename> class TComparer, typename TValueGetter>
        void Chain( const TValueGetter& getter ) {
    
            iComparers.push_back( std::make_pair( 
                boost::bind( getter, _1 ) == boost::bind( getter, _2 ), 
                boost::bind( TComparer<TValueGetter::result_type>(), boost::bind( getter, _1 ), boost::bind( getter, _2 ) ) 
            ) );
        }
    
        bool operator()( const T& lhs, const T& rhs ) {
            BOOST_FOREACH( const auto& comparer, iComparers ) {
                if( !comparer.first( lhs, rhs ) ) {
                    return comparer.second( lhs, rhs );
                }
            }
    
            return false;
        }
    
    private:
        std::vector<std::pair<EqualComparator, CustomComparator>> iComparers;
    };
    

    【讨论】:

    • 这段代码使用了 C++11 的 auto 等特性,并且在某些编译器上存在问题。 v2.cplusplus.com/forum/general/110335 中的线程给出了一个似乎解决了一些遗留问题的解决方案。
    【解决方案6】:

    C++ 11 中的可变参数模板提供了一个更短的选项:

        #include <iostream>
        using namespace std;
    
        struct vec { int x,y,z; };
    
        struct CmpX {
          bool operator() (const vec& lhs, const vec& rhs) const 
          { return lhs.x < rhs.x; }
        };
    
        struct CmpY {
          bool operator() (const vec& lhs, const vec& rhs) const 
          { return lhs.y < rhs.y; }
        };
    
        struct CmpZ {
          bool operator() (const vec& lhs, const vec& rhs) const 
          { return lhs.z < rhs.z; }
        };
    
        template <typename T>
        bool chained(const T &, const T &) {
          return false;
        }
    
        template <typename CMP, typename T, typename ...P>
        bool chained(const T &t1, const T &t2, const CMP &c, P...p) {
          if (c(t1,t2)) { return true;          }
          if (c(t2,t1)) { return false;         }
          else          { return chained(t1, t2, p...); }
        }
    
        int main(int argc, char **argv) {
          vec x = { 1,2,3 }, y = { 2,2,3 }, z = { 1,3,3 };
          cout << chained(x,x,CmpX(),CmpY(),CmpZ()) << endl;
          return 0;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-21
      • 2016-09-11
      • 1970-01-01
      • 2023-03-28
      • 2012-11-03
      • 1970-01-01
      • 2013-07-01
      • 1970-01-01
      相关资源
      最近更新 更多