【问题标题】:C++ lambdas for std::sort and std::lower_bound/equal_range on a struct element in a sorted vector of structs用于结构排序向量中结构元素上的 std::sort 和 std::lower_bound/equal_range 的 C++ lambda
【发布时间】:2011-05-15 04:58:53
【问题描述】:

我有这个结构的 std::vector:

struct MS
{        
  double aT;
  double bT;
  double cT;
};

我想使用 std::sort 以及 std::lower_bound/equal_range 等...

我需要能够对其进行排序并在结构的前两个元素中的任何一个上查找它。所以目前我有这个:

class MSaTLess 
{
public:
  bool operator() (const MS &lhs, const MS &rhs) const
  {
    return TLess(lhs.aT, rhs.aT);
  }
  bool operator() (const MS &lhs, const double d) const
  {
    return TLess(lhs.aT, d);
  }
  bool operator() (const double d, const MS &rhs) const
  {
    return TLess(d, rhs.aT);
  }
private:
  bool TLess(const double& d1, const double& d2) const
  {
    return d1 < d2;
  }
};


class MSbTLess 
{
public:
  bool operator() (const MS &lhs, const MS &rhs) const
  {
    return TLess(lhs.bT, rhs.bT);
  }
  bool operator() (const MS &lhs, const double d) const
  {
    return TLess(lhs.bT, d);
  }
  bool operator() (const double d, const MS &rhs) const
  {
    return TLess(d, rhs.bT);
  }
private:
  bool TLess(const double& d1, const double& d2) const
  {
    return d1 < d2;
  }
};

这允许我使用 MSaTLess() 调用 std::sort 和 std::lower_bound 以根据 aT 元素进行排序/查找,并使用 MSbTLess() 根据 bT 元素进行排序/查找。

我想摆脱仿函数并改用 C++0x lambda。对于相对简单的排序,因为 lambda 会将两个 MS 类型的对象作为参数。

但是对于 lower_bound 和其他二进制搜索查找算法呢?他们需要能够调用带有 (MS, double) 参数的比较器,以及相反的 (double, MS),对吗?在调用 lower_bound 时,如何最好地为这些提供 lambda?我知道我可以创建一个带有正在搜索的所需键值的 MS 虚拟对象,然后使用与 std::sort 相同的 lambda,但是有没有办法在不使用虚拟对象的情况下做到这一点?

【问题讨论】:

    标签: search sorting stl lambda c++11


    【解决方案1】:

    算法 std::sort、std::lower_bound 和 std::binary_search 采用比较容器的两个元素的谓词。任何比较两个 MS 对象并在它们按顺序返回 true 的 lambda 都应该适用于所有三种算法。

    【讨论】:

      【解决方案2】:

      这有点尴尬,但是如果您从标准中检查lower_bound 和upper_bound 的定义,您会看到lower_bound 的定义将取消引用的迭代器作为first 比较的参数(和第二个值),而upper_bound 放置取消引用的迭代器 second(和值在前)。

      所以,我没有对此进行测试,但我想你会想要:

      std::lower_bound(vec.begin(), vec.end(), 3.142, [](const MS &lhs, double rhs) {
          return lhs.aT < rhs;
      });
      

      和

      std::upper_bound(vec.begin(), vec.end(), 3.142, [](double lhs, const MS &rhs) {
          return lhs < rhs.aT;
      });
      

      这非常令人讨厌,如果不查找更多内容,我不确定您是否真的有权假设实现仅以文本中描述的方式使用比较器 - 这是结果的定义,而不是到达那里的手段。它对binary_search 或equal_range 也没有帮助。

      在 25.3.3.1 中并没有明确说明迭代器的值类型必须可转换为 T,但在某种程度上暗示了算法的要求是 T(在这种情况下, double) 必须是 LessThanComparable,而不是 T 必须以任何特定顺序与迭代器的值类型相当。

      所以我认为最好始终使用比较两个 MS 结构的 lambda(或仿函数),而不是将 double 作为值传递,而是传递一个虚拟 MS,并将正确的字段设置为您正在寻找的值为:

      std::upper_bound(vec.begin(), vec.end(), MS(3.142,0,0), [](const MS &lhs, const MS &rhs) {
          return lhs.aT < rhs.aT;
      });
      

      如果您不想给 MS 一个构造函数(因为您希望它是 POD),那么您可以编写一个函数来创建您的 MS 对象:

      MS findA(double d) {
          MS result = {d, 0, 0};
          return result;
      }
      MS findB(double d) {
          MS result = {0, d, 0};
          return result;
      }
      

      真的,既然有了 lambda,我们就需要一个带有一元“比较器”的二进制搜索版本:

      double d = something();
      unary_upper_bound(vec.begin(), vec.end(), [d](const MS &rhs) {
          return d < rhs.aT;
      });
      

      但是,C++0x 没有提供它。

      【讨论】:

      • 我刚刚查看了 MSVC 实现,std::binary_search 调用了 std::lower_bound。问题是每个人都以不同的顺序调用带有参数的谓词。所以第一个例子看起来行不通。
      • @Blastfurnace:是的,如果它不起作用我不会感到惊讶,尽管我认为到目前为止你所展示的只是binary_search 需要一个“双向”工作的比较器,而 lower_bound 可能只需要它以一种方式工作。如果是lower_bound 调用了binary_search,这意味着lower_bound 也需要它双向工作,但在另一个方向上的依赖关系至少仍然是合理的。
      • 你是对的。一时兴起,我检查了 MSVC std::upper_bound,它以与 std::binary_search 相同的顺序传递参数。我今天学到的教训是避免让自己感到困惑,并假设我的谓词必须比较容器类型的两个对象。
      • 相当蹩脚,他们以相反的方式定义
      • @SteveLorimer:如果我没记错的话,这是必要的,这是因为两种算法都采用“小于”比较,这反过来又是因为这是唯一一种比较标准算法曾经使用过的。
      【解决方案3】:

      与您所说的关于 lambda 的内容没有直接关系,但这可能是使用二分搜索功能的一个想法:

      #include <iostream>
      #include <algorithm>
      #include <vector>
      
      struct MS
      {
          double aT;
          double bT;
          double cT;
          MS(double a, double b, double c) : aT(a), bT(b), cT(c) {}
      };
      
      // template parameter is a data member of MS, of type double
      template <double MS::*F>
      struct Find {
          double d;
          Find(double d) : d(d) {}
      };
      
      template <double MS::*F>
      bool operator<(const Find<F> &lhs, const Find<F> &rhs) {
          return lhs.d < rhs.d;
      }
      template <double MS::*F>
      bool operator<(const Find<F> &lhs, const MS &rhs) {
          return lhs.d < rhs.*F;
      }
      template <double MS::*F>
      bool operator<(const MS &lhs, const Find<F> &rhs) {
          return lhs.*F < rhs.d;
      }
      
      int main() {
          std::cout << (Find<&MS::bT>(1) < Find<&MS::bT>(2)) << "\n";
          std::cout << (Find<&MS::bT>(1) < MS(1,0,0)) << "\n";
          std::cout << (MS(1,0,0) < Find<&MS::bT>(1)) << "\n";
      
          std::vector<MS> vec;
          vec.push_back(MS(1,0,0));
          vec.push_back(MS(0,1,0));
          std::lower_bound(vec.begin(), vec.end(), Find<&MS::bT>(0.5));
          std::upper_bound(vec.begin(), vec.end(), Find<&MS::bT>(0.5));
      }
      

      基本上,通过使用Find 作为值,我们不必提供比较器,因为Find 使用我们指定的字段与MS 进行比较。这与您在此处看到的答案相同:how to sort STL vector,但在这种情况下使用值而不是比较器。不确定它是否很好用,但它可能是,因为它指定了要搜索的值和要搜索的字段在一个简短的表达式中。

      【讨论】:

        【解决方案4】:

        std::equal_range 我遇到了同样的问题,并想出了一个替代解决方案。

        我有一组指向按类型字段排序的对象的指针。我需要找到给定类型的对象范围。

        const auto range = std::equal_range (next, blocks.end(), nullptr,
            [type] (Object* o1, Object* o2)
        {
            return (o1 ? o1->Type() : type) < (o2 ? o2->Type() : type);
        });
        

        虽然它比专用谓词效率低,因为它为我的集合中的每个对象引入了不必要的 nullptr 测试,但它确实提供了一个有趣的替代方案。

        顺便说一句,当我在您的示例中使用类时,我倾向于执行以下操作。除了更短之外,这还允许我添加其他类型,每种类型只有 1 个函数,而不是每种类型 4 个运算符。

        class MSbTLess 
        {
        private:
            static inline const double& value (const MS& val)
            {
                return val.bT;
            }
        
            static inline const double& value (const double& val)
            {
                return val;
            }
        
        public:
            template <typename T1, typename T2>
            bool operator() (const T1& lhs, const T2& rhs) const
            {
                return value (t1) < value (t2);
            }
        };
        

        【讨论】:

          【解决方案5】:

          在definition of lower_bound 和其他 STL 算法中,Compare 函数的第一个类型必须与 Forward Iterator 的类型匹配,而第二个类型必须与 T 的类型(即值)匹配。

          template< class ForwardIt, class T, class Compare >
          ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
          

          因此,确实可以比较来自不同对象的事物(执行另一个响应称为一元比较器的操作)。在 C++11 中:

          vector<MS> v = SomeSortedVectorofMSByFieldaT();
          double a_key;
          auto it = std::lower_bound(v.begin(), 
                                     v.end(), 
                                     a_key, 
                                     []{const MS& m, const double& a) {
                                       m.aT < a; 
                                     });
          

          这也可以与其他 STL 算法函数一起使用。

          【讨论】:

          • 此代码无法编译。将 '[]' 之后的 '{' 更改为 '(' 并将 'return' 语句添加到 ' m.aT
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-03-06
          • 2018-05-16
          • 1970-01-01
          • 2010-10-10
          • 1970-01-01
          • 1970-01-01
          • 2014-02-27
          相关资源
          最近更新 更多