对std :: binary_search的神秘限制

时间:2011-08-02 22:20:32

标签: c++ algorithm search stl standards

问题描述:
考虑一些具有std::string name成员的结构。为清楚起见,我们假设它是struct Human,表示有关人的信息。除了name之外,它还可以有许多其他数据成员 让容器std::vector<Human> vec,其中对象已经按name排序。同样为了清楚起见,假设所有名称都是唯一的 问题是:让一些字符串nameToFind找出数组中是否存在具有此名称的元素。

解决方案和我的进步:
显而易见且自然的解决方案似乎使用std::binary_search函数执行二进制搜索。但是有一个问题:被搜索的元素的类型(std::string)与容器中的元素类型(Human)不同,而std :: binary_search需要一个规则来比较这些元素。元素。我尝试用三种方式解决这个问题,如下所述。前两个是为了说明我的解决方案的演变和我遇到的问题。我的主要问题涉及第三个问题。

尝试1:将std::string转换为Human

写一个比较函数:

bool compareHumansByNames( const Human& lhs, const Human& rhs )
{
   return lhs.name < rhs.name;
}

然后添加一个构造函数,该构造函数构造Human的{​​{1}}对象:

std::string

并使用以下格式的binary_search:

struct Human
{
   Human( const std::string& s );
   //... other methods

   std::string name;
   //... other members
};

似乎工作,但出现了两个大问题:
首先,如何初始化其他数据成员,但std::binary_search( vec.begin(), vec.end(), nameToFind, compareHumansByNames ); ,尤其是在他们没有默认构造函数的情况下?设置魔术值可能会导致创建一个在语义上非法的对象 其次,我们必须将此构造函数声明为非Human::name,以允许在算法期间进行隐式转换。这种不良后果众所周知 此外,这样一个临时的explicit对象将在每次迭代时构建,结果可能会非常昂贵。

尝试2:将Human转换为Human

我们可以尝试将std::string添加到operator string ()类,返回它的Human,然后使用两个name的比较。然而,由于以下原因,这种方法也很不方便:

首先,由于讨论的问题here,代码不会立即编译。我们将需要更多工作来使编译器使用适当的std::string 第二,什么意思是“将人类转换为字符串”?这种转换的存在可能导致类operator <的语义错误使用,这是不可取的。

尝试3:在没有转化的情况下进行比较。

到目前为止,我得到的最佳解决方案是创建一个

Human

并使用二进制搜索

struct Comparator
{
   bool operator() ( const Human& lhs, const std::string& rhs )
   {
      return lhs.name < rhs;
   }
   bool operator() ( const std::string& lhs, const Human& rhs )
   {
      return lhs < rhs.name;
   }
};

这个编译并正确执行,一切似乎都没问题,但这里有趣的部分开始了:

看看http://www.sgi.com/tech/stl/binary_search.html。这里说“ ForwardIterator 的值类型与T 类型相同。”。相当混乱的限制,我的最后一个解决方案打破了它。让我们看看C ++标准对它的评价:


25.3.3.4 binary_search

binary_search( vec.begin(), vec.end(), nameToFind, Comparator() );

要求:类型T是LessThanComparable(20.1.2)。


没有明确说明template<class ForwardIterator, class T> bool binary_search(ForwardIterator first, ForwardIterator last, const T& value); template<class ForwardIterator, class T, class Compare> bool binary_search(ForwardIterator first, ForwardIterator last, const T& value, Compare comp); 的类型。但是,在 20.1.2 中给出的 LessThanComparable 的定义中,有关于相同类型的两个元素的比较。这是我不明白的。它确实意味着被搜索对象的类型和容器对象的类型 必须是相同的,我的解决方案打破了这个限制?或者它不是指使用ForwardIterator比较器的情况,而只是关于默认comp用于比较的情况?在第一种情况下,我很困惑如何使用operator <来解决这个问题,而不会遇到上面提到的问题。

提前感谢您的帮助,并抽出时间阅读我的问题。

注意:据我所知,手工编写二进制搜索不需要时间,可以立即解决问题,但为了避免重新发明轮子,我想使用std :: binary_search。我也很有兴趣根据标准找出这种限制的存在。

4 个答案:

答案 0 :(得分:12)

如果您的目标是找出具有指定名称的Human,那么以下内容应该可以肯定:

const std::string& get_name(const Human& h)
{
    return h.name;
}

...

bool result = std::binary_search(
    boost::make_transform_iterator(v.begin(), &get_name),
    boost::make_transform_iterator(v.end(), &get_name),
    name_to_check_against);

答案 1 :(得分:5)

[完全重写;无视评论]

措辞已从C ++ 03更改为C ++ 0x。在后者中,没有更多要求T低于可比性,可能是为了减轻这种不必要的限制。

新标准仅要求comp(e, value)隐含!comp(value, e)。因此,只要您的比较器实现两个方向,您就应该能够合法地使用比较器函数搜索string作为值,该比较器函数实现两个非对称比较(即您的“尝试3”)。

答案 2 :(得分:0)

我认为标准在这里说的是表达式fucntor(a, b)需要是一个有效的严格弱排序,无论算法是否决定做functor(*begin, *(begin + 1))之类的事情。因此,我认为您的比较器需要提供operator()(Human, Human)的重载才能符合要求。

那就是说,我认为这是one of those things not explicitly allowed by the standard, but for which few or no implementations exist which take advantage of the latitude offered by the standard

答案 3 :(得分:0)

我认为标准中的任何地方都没有要求<传递给比较函数(或binary_search运算符)的值的类型必须相同。所以,正式地说我认为使用一个适用于两种不同类型值的比较器是完全没问题的。