在结构化的排序向量中的struct元素上的std :: sort和std :: lower_bound / equal_range的C ++ lambdas

时间:2010-11-24 16:08:41

标签: search sorting stl lambda c++11

我有一个这个结构的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 lambdas。对于相对简单的排序,因为lambda将两个MS类型的对象作为参数。

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

5 个答案:

答案 0 :(得分:16)

这有点尴尬,但如果你从标准中检查lower_boundupper_bound的定义,你会发现lower_bound的定义将解除引用的迭代器作为< em>比较的第一个参数(和第二个值),而upper_bound将解除引用的迭代器第二个(和值第一个)。

所以,我没有测试过这个,但我认为你想要:

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_searchequal_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不提供它。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

与你对lambdas的看法没有直接关系,但这可能是使用二进制搜索功能的一个想法:

#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,但在这种情况下使用值而不是比较器。不确定它是否真的好用,但它可能是,因为它指定要搜索的值和要在单个短表达式中搜索的字段。

答案 3 :(得分:0)

我对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);
    }
};

答案 4 :(得分:0)

definition of lower_bound和其他STL算法中,比较函数是这样的,第一种类型必须与前向迭代器的类型匹配,第二种类型必须与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算法函数一起使用。