【问题标题】:Is there support in C++/STL for sorting objects by attribute?C++/STL 中是否支持按属性对对象进行排序?
【发布时间】:2011-01-13 05:58:56
【问题描述】:

我想知道 STL 是否支持这个:

假设我有这样的课程:

class Person
{
public:
  int getAge() const;
  double getIncome() const;
  ..
  ..
};

还有一个向量:

vector<Person*> people;

我想按年龄对人的向量进行排序: 我知道我可以通过以下方式做到这一点:

class AgeCmp
{
public:
   bool operator() ( const Person* p1, const Person* p2 ) const
   {
     return p1->getAge() < p2->getAge();
   }
};
sort( people.begin(), people.end(), AgeCmp() );

有没有更简洁的方法来做到这一点?仅仅因为我想根据“属性”进行排序,就必须定义一个完整的类似乎有点过头了。大概是这样的吧?

sort( people.begin(), people.end(), cmpfn<Person,Person::getAge>() );

【问题讨论】:

  • C++0x 具有 lambda 函数,并且 TR1 添加了 &lt;tr1/functional&gt; 标头,这使得它不再那么冗长。
  • +1 很好地询问和回答。这应该是未来重复链接到的帖子。
  • @Omnifarious:你确定在这个例子中 TR1/functional 中的某些东西对你在 C++03 中已经可以做的事情有所帮助吗?
  • @Manuel,好吧,你可以手动完成。但是,如果我没记错的话,TR1/functional 可以更轻松地使用更广泛的事物作为函子。

标签: c++ sorting stl attributes


【解决方案1】:

如果您只想按一件事对人们进行排序(或者如果有一个合理的默认值您大部分时间都想使用),请将 operator&lt; 覆盖为 People按此属性排序的类。如果没有显式比较器,STL 排序函数(以及任何隐式使用排序的函数,如集合和映射)将使用operator&lt;

当您想按operator&lt; 以外的其他内容进行排序时,您描述的方式是当前 C++ 版本的唯一方法(尽管比较器可以只是一个常规函数;它没有成为函子)。 C++0x 标准通过允许lambda functions 来减少冗长。

如果您不愿意等待 C++0x,另一种方法是使用 boost::lambda

【讨论】:

  • 对 People pointers 的向量向量进行排序永远不会使用 People::operator
  • 如果你想获得技术,标准库中的比较不直接使用 operatorstd::less<T>,默认使用operator&lt;。但是,您可以将 std::less&lt;T&gt; 专门用于一种类型,并在该实现中使用您想要的任何内容。
  • 不幸的是它必须是一个独立的函数,而不是类的成员。
  • “C++0x 标准会让这个变得不那么冗长” - 不那么冗长,可能,更可怕的语法,绝对。
  • :) 我不认为它会那么可怕[]( Person* l, Person* r ) =&gt; bool { return l-&gt;age() &lt; r-&gt;age(); }。嗯……没关系……
【解决方案2】:

您可以只拥有一个全局函数或静态函数。这些全局或静态函数中的每一个都与一个属性进行比较。不需要上课。保存论文进行比较的一种方法是使用 boost bind,但 bind 仅用于查找所有类或将所有类与某个绑定参数进行比较。跨多个元素存储数据是制作函子的唯一原因。

编辑:另见 boost lambda 函数,但它们仅适用于简单函数。

【讨论】:

    【解决方案3】:

    您无需创建类 - 只需编写一个函数:

    #include <vector>
    #include <algorithm>
    using namespace std;
    
    struct Person {
        int age;
        int getage() const {
            return age;
        }
    };
    
    bool cmp( const Person * a, const Person * b ) {
        return a->getage() < b->getage() ;
    }
    
    int main() {
        vector <Person*> v;
        sort( v.begin(), v.end(), cmp );
    }
    

    【讨论】:

    • 请注意,在许多(大多数?)情况下,使用该函数的排序最终会运行得更慢。
    • 运行比什么慢?不排序?
    • @leeeroy:运行速度比使用仿函数慢。
    • @Jeffry 我用 VC2008 做了很多测试,它内联函数就像函子一样。当然,这不是一个规则,我同意内联函数对编译器来说是一项更难的工作。
    • @AraK:看我的回答——那里的代码演示了我在说什么。不要误会我的意思:可能存在允许两者匹配的编译器标志,但如果是这样,我完全不确定大多数人是否知道要使用什么标志,或者大部分时间都这样做。
    【解决方案4】:

    根据成员属性进行比较的通用适配器。虽然它在第一次可重用时更加冗长。

    // Generic member less than
    template <typename T, typename M, typename C>
    struct member_lt_type 
    {
       typedef M T::* member_ptr;
       member_lt_type( member_ptr p, C c ) : ptr(p), cmp(c) {}
       bool operator()( T const & lhs, T const & rhs ) const 
       {
          return cmp( lhs.*ptr, rhs.*ptr );
       }
       member_ptr ptr;
       C cmp;
    };
    
    // dereference adaptor
    template <typename T, typename C>
    struct dereferrer
    {
       dereferrer( C cmp ) : cmp(cmp) {}
       bool operator()( T * lhs, T * rhs ) const {
          return cmp( *lhs, *rhs );
       }
       C cmp;
    };
    
    // syntactic sugar
    template <typename T, typename M>
    member_lt_type<T,M, std::less<M> > member_lt( M T::*ptr ) {
       return member_lt_type<T,M, std::less<M> >(ptr, std::less<M>() );
    }
    
    template <typename T, typename M, typename C>
    member_lt_type<T,M,C> member_lt( M T::*ptr, C cmp ) {
       return member_lt_type<T,M,C>( ptr, cmp );
    }
    
    template <typename T, typename C>
    dereferrer<T,C> deref( C cmp ) {
       return dereferrer<T,C>( cmp );
    }
    
    // usage:    
    struct test { int x; }
    int main() {
       std::vector<test> v;
       std::sort( v.begin(), v.end(), member_lt( &test::x ) );
       std::sort( v.begin(), v.end(), member_lt( &test::x, std::greater<int>() ) );
    
       std::vector<test*> vp;
       std::sort( v.begin(), v.end(), deref<test>( member_lt( &test::x ) ) );
    }
    

    【讨论】:

      【解决方案5】:

      我看到 dribeas 已经发布了这个想法,但既然我已经写过了,下面是你如何编写一个通用比较器来使用 getter 函数的方法。

      #include <functional>
      
      template <class Object, class ResultType>
      class CompareAttributeT: public std::binary_function<const Object*, const Object*, bool>
      {
          typedef ResultType (Object::*Getter)() const;
          Getter getter;
      public:
          CompareAttributeT(Getter method): getter(method) {}
          bool operator()(const Object* lhv, const Object* rhv) const
          {
              return (lhv->*getter)() < (rhv->*getter)();
          }
      };
      
      template <class Object, class ResultType>
      CompareAttributeT<Object, ResultType> CompareAttribute(ResultType (Object::*getter)() const)
      {
          return CompareAttributeT<Object, ResultType>(getter);
      }
      

      用法:

      std::sort(people.begin(), people.end(), CompareAttribute(&Person::getAge));
      

      我认为为非指针重载operator() 可能是个好主意,但是不能通过从binary_function 继承来对argument_types 进行类型定义——这可能不是一个很大的损失,因为它很难无论如何都需要在需要的地方使用它,例如,无论如何都无法否定比较函子。

      【讨论】:

      • 由于标准已经有“mem_fn”和“mem_fun”,最好将其称为“mem_fn_cmp”而不是“CompareAttribute”。
      • 随便你喜欢什么(虽然这是一件好事,但我通常自己最终还是使用 boost.bind 之类的)。
      【解决方案6】:

      这本身并不是一个真正的答案,作为对 AraK 对我评论的回复的回复,即使用函数而不是仿函数进行排序可能会更慢。下面是一些比较各种排序的(诚然丑陋——CnP 太多)测试代码:qsort、std::sort of vector vs. array,以及 std::sort 使用模板类、模板函数或普通函数进行比较:

      #include <vector>
      #include <algorithm>
      #include <stdlib.h>
      #include <time.h>
      
      int compare(void const *a, void const *b) {
          if (*(int *)a > *(int *)b)
              return -1;
          if (*(int *)a == *(int *)b)
              return 0;
          return 1;
      }
      
      const int size = 200000;
      
      typedef unsigned long ul;
      
      void report(char const *title, clock_t ticks) { 
          printf("%s took %f seconds\n", title, ticks/(double)CLOCKS_PER_SEC);
      }
      
      void wait() { 
          while (clock() == clock())
              ;
      }
      
      template <class T>
      struct cmp1 { 
          bool operator()(T const &a, T const &b) { 
              return a < b;
          }
      };
      
      template <class T>
      bool cmp2(T const &a, T const &b) { 
          return a<b;
      }
      
      bool cmp3(int a, int b) { 
          return a<b;
      }
      
      int main(void) {
          static int array1[size];
          static int array2[size];
      
          srand(time(NULL));
      
          for (int i=0; i<size; i++) 
              array1[i] = rand();
      
          const int iterations = 100;
      
          clock_t total = 0;
      
          for (int i=0; i<iterations; i++) { 
              memcpy(array2, array1, sizeof(array1));
              wait();
              clock_t start = clock();
              qsort(array2, size, sizeof(array2[0]), compare);
              total += clock()-start;
          }
          report("qsort", total);
      
          total = 0;
          for (int i=0; i<iterations; i++) {
              memcpy(array2, array1, sizeof(array1));
              wait();
              clock_t start = clock();
              std::sort(array2, array2+size);
              total += clock()- start;
          }
          report("std::sort (array)", total);
      
          total = 0;
          for (int i=0; i<iterations; i++) {
              memcpy(array2, array1, sizeof(array1));
              wait();
              clock_t start = clock();
              std::sort(array2, array2+size, cmp1<int>());
              total += clock()- start;
          }
          report("std::sort (template class comparator)", total);
      
          total = 0;
          for (int i=0; i<iterations; i++) {
              memcpy(array2, array1, sizeof(array1));
              wait();
              clock_t start = clock();
              std::sort(array2, array2+size, cmp2<int>);
              total += clock()- start;
          }
          report("std::sort (template func comparator)", total);
      
          total = 0;
          for (int i=0; i<iterations; i++) {
              memcpy(array2, array1, sizeof(array1));
              wait();
              clock_t start = clock();
              std::sort(array2, array2+size, cmp3);
              total += clock()- start;
          }
          report("std::sort (func comparator)", total);
      
          total = 0;
          for (int i=0; i<iterations; i++) {
              std::vector<int> array3(array1, array1+size);
              wait();
              clock_t start = clock();
              std::sort(array3.begin(), array3.end());
              total += clock()-start;
          }
          report("std::sort (vector)", total);
      
          return 0;
      } 
      

      使用cl /O2b2 /GL sortbench3.cpp 用VC++ 9/VS 2008 编译,我得到:

      qsort took 3.393000 seconds
      std::sort (array) took 1.724000 seconds
      std::sort (template class comparator) took 1.725000 seconds
      std::sort (template func comparator) took 2.725000 seconds
      std::sort (func comparator) took 2.505000 seconds
      std::sort (vector) took 1.721000 seconds
      

      我相信这些相当干净地分为三组:使用带有默认比较的排序,以及使用模板类生成最快的代码。使用函数或模板函数显然更慢。使用 qsort 是(令某些人惊讶的)最慢的,大约 2:1 的差距。

      cmp2 和 cmp3 之间的差异似乎完全源于按引用传递与值传递——如果您将 cmp2 更改为按值获取其参数,它的速度与 cmp3 完全匹配(至少在我的测试中)。不同之处在于,如果您知道类型将是 int,那么您几乎肯定会按值传递,而对于泛型 T,您通常会通过 const 引用(以防万一它更昂贵)复制)。

      【讨论】:

      • @Jerry 我同意你的观点,使用仿函数是正确的做法。我当然更喜欢这种解决方案,但正如我所说,我的(糟糕的)测试不是一个规则。 +1 用于显示您的测试结果。顺便说一句,很抱歉在我的第一条评论中拼错了你的名字:)
      • @AraK:我并没有强烈主张仿函数是“正确”的事情,因为可以进行权衡,所以仿函数的“冗长” 可能值得。拼写很好——它似乎经常发生(虽然更经常用我的姓——当我还是个孩子的时候,我送报纸,人们发现用支票付款发现了真正有创意的拼写方式。. .)
      • cmp3 和以前的比较器之间还有一个区别。 cmp3 按值接受参数。您能尝试一个通过引用获取整数的函数 cmp4 吗?
      • @Maciej H:当然。通过引用获取其参数的普通函数会稍微慢一些,因此它比通过引用获取其参数的函数模板稍慢(不是很多,但差异似乎非常可靠 - 我得到〜2.8秒,而〜2.7函数模板,两者都通过引用获取参数)。
      • 谢谢。我必须承认,起初我并没有意识到,函数模板实际上是我要求的测试。现在我对差异感到惊讶:)。哦,好吧,我得到了教训。
      【解决方案7】:

      我只是根据 UncleBens 和 david-rodriguez-dribeas 的想法尝试了这个。

      这似乎(按原样)与我当前的编译器一起工作。 g++ 3.2.3。请让我知道它是否适用于其他编译器。

      #include <vector>
      #include <algorithm>
      #include <iostream>
      
      using namespace std;
      
      class Person
      {
      public:
          Person( int _age )
              :age(_age)
          {
          }
          int getAge() const { return age; }
      private:
          int age;
      };
      
      template <typename T, typename ResType>
      class get_lt_type
      {
          ResType (T::*getter) () const;
      public:
          get_lt_type(ResType (T::*method) () const ):getter(method) {}
          bool operator() ( const T* pT1, const T* pT2 ) const
          {
              return (pT1->*getter)() < (pT2->*getter)();
          }
      };
      
      template <typename T, typename ResType>
      get_lt_type<T,ResType> get_lt( ResType (T::*getter) () const ) {
          return get_lt_type<T, ResType>( getter );
      }
      
      int main() {
          vector<Person*> people;
          people.push_back( new Person( 54 ) );
          people.push_back( new Person( 4 ) );
          people.push_back( new Person( 14 ) );
      
          sort( people.begin(), people.end(), get_lt( &Person::getAge) );
      
          for ( size_t i = 0; i < people.size(); ++i )
          {
              cout << people[i]->getAge() << endl;
          }
          // yes leaking Persons
          return 0;
      }
      

      【讨论】:

        【解决方案8】:

        尽管我喜欢模板的想法,但这些答案都非常冗长!只需使用 lambda 函数,它让事情变得更简单!

        你可以用这个:

        sort( people.begin(), people.end(), []( Person a, Person b ){ return a.age < b.age; } );
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-02-08
          • 2013-02-12
          • 2023-04-03
          • 2011-03-21
          • 1970-01-01
          • 2021-11-01
          • 2017-08-12
          相关资源
          最近更新 更多