【问题标题】:Mystical restriction on std::binary_search对 std::binary_search 的神秘限制
【发布时间】:2011-10-18 15:56:13
【问题描述】:

问题描述:
考虑一些具有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;
}

然后添加一个构造函数,它从std::string 构造一个Human 对象:

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

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

并以下列形式使用 binary_search:

std::binary_search( vec.begin(), vec.end(), nameToFind, compareHumansByNames );

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

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

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

首先,由于here 讨论的问题,代码不会立即编译。我们将不得不做更多工作以使编译器使用适当的operator &lt;
其次,“将人类转换为字符串”是什么意思?这种转换的存在会导致Human 类在语义上的错误使用,这是不可取的。

尝试 3:比较没有转化。

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

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;
   }
};

并使用二分查找

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

这编译和执行正确,一切似乎都正常,但有趣的部分从这里开始:

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


25.3.3.4 binary_search

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);

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


没有明确说明ForwardIterator 的类型。但是,在20.1.2 中给出的LessThanComparable 的定义中,提到了相同类型 的两个元素的比较。这是我不明白的。是否确实意味着正在搜索的对象的类型容器对象的类型必须相同,而我的解决方案打破了这一点限制 ?或者它不涉及使用comp比较器的情况,而仅涉及使用默认operator &lt;进行比较的情况?在第一种情况下,我对如何使用std::binary_search 来解决这个问题而不遇到上述问题感到困惑。

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

注意:我知道手动编写二分搜索不需要时间,并且会立即解决问题,但为了避免重新发明轮子,我想使用 std::binary_search。根据标准了解这种限制的存在对我来说也很有趣。

【问题讨论】:

  • 我没有 C++98 或 03 标准的副本,但我的 C++0x 标准版本没有说明它们需要是 LessThanComparable。只有operator&lt; 可以在两边进行比较(或者在比较函子版本的情况下,operator())。所以 C++0x 似乎已经消除了歧义,您的 binary_search 实现领先于曲线。
  • 有趣的问题。出于好奇(为了后代),当您说“这可以正确编译和执行,一切正常。”时,您用什么实现进行了测试?
  • 我认为 C++0x FDIS 并没有说任何限制这种类型的东西,只谈到了e &lt; valuecomp(e, value) 的订购要求,其中e 在范围和value 是发现者。 (我想这就是尼科尔已经说过的。)
  • @André Caron:在 g++ 和 MSVS 上。实际上,我怀疑是否存在一个不起作用的实现,但我想了解标准在这个问题中的立场。
  • @Grigor:我也怀疑你会找到一个它不起作用的实现,但我仍然认为当某些东西声称可以工作时,说哪个实现是相关的。跨度>

标签: c++ algorithm search stl standards


【解决方案1】:

如果您的目标是查找是否存在具有给定名称的 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);

【讨论】:

  • 好吧,只要 boost 可用,就符合标准。 :) 请注意,您可能可以将 std::ptr_fun 和单独的方法替换为 std::mem_fun_ref(&amp;Human::GetName) 或类似的方法,具体取决于 Human 当然具有访问器。
  • 这很棒。这是否保留了迭代器的随机访问特性,从而使二分搜索仍然快速?
  • @Kerrek: yes it does.
  • @Billy:是的,确实如此。你也可能有一个ptr_mem 类助手来自动制造这样一个方便的一元仿函数。这是我一直发现标准库中缺少的东西。
  • 我喜欢这个解决方案! binary_searchlower_bound 算法的问题在于,您只能按值搜索 findee,因此如果容器的值类型昂贵或无法构造用于查找目的,那么更改迭代器似乎是唯一的明智的解决方案。
【解决方案2】:

[完全重写;无视cmets]

措辞已从 C++03 更改为 C++0x。在后者中,不再要求T 小于可比,大概是为了减轻这种不必要的限制。

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

【讨论】:

  • @Billy:哦,废话,你说得对。起初我是用find_if 写的,但它没有同样的复杂性......
  • 这没有回答问题。问题是关于使用与容器中对象类型不同类型的搜索值的合法性。在您的情况下,这些类型是 NameFinder 和 T。
  • @Grigor:嗯,在我的例子中,类型是Tstd::string,不是吗?但我知道这是错误的,我正在想办法解决它。
  • 不是 Tstd::string 而是 TNameFinder - 这是您在 binary_search 中用作第三个参数的类型。
  • @Kerrek:它也不适用于find_if——find_if 需要一个相等比较器;你提供了一个小于比较器。
【解决方案3】:

我认为标准在这里所说的是表达式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

【讨论】:

    【解决方案4】:

    我认为标准中的任何地方都没有要求binary_search 传递给比较函数(或&lt; 运算符)的值的类型必须相同。因此,从形式上讲,我认为使用可处理两种不同类型值的比较器是完全可以的。

    【讨论】:

    • 是的,没有明确说明,但可能来自Requires: Type T is LessThanComparable (20.1.2). 这就是我的问题所在。
    • 我认为binary_search 可以直接在两个Humans 之间进行比较,在这种情况下,他需要再过载一个。
    • 重点是搜索类型(std::string)与容器元素的类型(Human)不同,这就是LessThanComparable的要求产生问题的地方,因为有不是单一类型T,而是存在两种不同的类型。
    • @Grigor Gevorgyan:是的,但我看不出有什么理由比它所说的更多。它要求T 类型为 LessThanComparable?伟大的。让我们确保满足该要求。不过,我不明白它如何禁止混合比较。
    • @AndreyT:我的困惑在于——如果T 必须是LessThanComparable,这意味着应该比较T 类型的对象,不是吗?如果是这样,这可以解释为搜索对象的类型和容器元素的类型应该是相同的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-16
    • 1970-01-01
    • 2013-03-11
    • 2013-08-13
    • 1970-01-01
    • 1970-01-01
    • 2015-06-29
    相关资源
    最近更新 更多