【问题标题】:Comparison tricks in C++C++中的比较技巧
【发布时间】:2014-07-09 07:28:55
【问题描述】:

一个类:

class foo{
public:
    int data;
};

现在我想给这个类添加一个方法,做一些比较,看看它的数据是否等于给定的数字之一。

当然,我可以写if(data==num1|| data == num2|| data ==num3.....),但老实说,当我写data ==时,我每次与数字比较时都会感到恶心。

所以,我希望我能写出这样的东西:

if(data is equal to one of these(num1,num2,num3,num4,num5...))
    return true;
else
    return false;

我要实现这条语句,data is equal to one of these(num1, num2, num3, num4, num5...)

这是我的方法:

#include <stdarg.h>
bool is_equal_to_one_of_these(int count,...){
    int i;
    bool equal = false;
    va_list arg_ptr;
    va_start(arg_prt,count);
    for(int x=0;x<count;x++){
        i = va_arg(arg_ptr,int);
        if( i == data ){
            equal = true;
            break;
        }
    }
    va_end(arg_ptr);
    return equal;
}

这段代码将为我完成这项工作。但是每次使用这种方法时,我都要统计参数并传入。

有人有更好的主意吗?

【问题讨论】:

  • if (1 &lt;= data &amp;&amp; data &lt;= 6)
  • $data == any(@numbers) 哦等等,这是 perl 6 :P。使用运算符重载来模拟会很有趣,尽管
  • 为什么要检查 i != data 并继续?为什么不只检查i == data,并删除i != data 检查,因为循环无论如何都会继续?
  • 当我写这段代码时我的大脑有点短:D,我会编辑它。感谢您指出〜@TomHeard

标签: c++ comparison


【解决方案1】:

简单的方法

最简单的方法是在std::find 周围编写一个名为in() 的成员函数包装器,并使用一对迭代器来查找有问题的数据。我为此写了一个简单的template&lt;class It&gt; in(It first, It last) 成员函数

template<class It>
bool in(It first, It last) const
{
    return std::find(first, last, data) != last;
}

如果你无权访问foo的源,你可以写一个签名template&lt;class T&gt; bool in(foo const&amp;, std::initializer_list&lt;T&gt;)等的非成员函数,像这样调用它

in(f, {1, 2, 3 });

艰难的路

但是让我们完全过火:只需再添加两个public 重载:

  • 一个采用std::initializer_list 参数,该参数使用相应初始化列表参数的begin()end() 迭代器调用前一个参数。
  • 一个用于任意容器作为输入,它将对detail_in() 助手的另外两个private 重载执行一点标记调度
    • 一个重载使用尾随返回类型decltype(c.find(data), bool()) 执行 SFINAE 技巧,如果相关容器 c 没有成员函数 find(),则将从重载集中删除,否则返回 bool(这是通过滥用decltype中的逗号运算符实现的)
    • 一个后备重载,它简单地采用 begin()end() 迭代器并委托给原始 in() 采用两个迭代器

因为detail_in() 帮助器的标签形成了一个继承层次结构(很像标准迭代器标签),第一个重载将匹配关联容器std::setstd::unordered_set 及其多个表亲。所有其他容器,包括 C 数组、std::arraystd::vectorstd::list,都将匹配第二个重载。

#include <algorithm>
#include <array>
#include <initializer_list>
#include <type_traits>
#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>

class foo
{
public:
    int data;

    template<class It>
    bool in(It first, It last) const
    {
        std::cout << "iterator overload: ";
        return std::find(first, last, data) != last;
    }

    template<class T>
    bool in(std::initializer_list<T> il) const
    {
        std::cout << "initializer_list overload: ";
        return in(begin(il), end(il));
    }

    template<class Container>
    bool in(Container const& c) const 
    {
        std::cout << "container overload: ";
        return detail_in(c, associative_container_tag{});    
    }

private:
    struct sequence_container_tag {};
    struct associative_container_tag: sequence_container_tag {};

    template<class AssociativeContainer>
    auto detail_in(AssociativeContainer const& c, associative_container_tag) const 
    -> decltype(c.find(data), bool())
    {
        std::cout << "associative overload: ";
        return c.find(data) != end(c);    
    }

    template<class SequenceContainer> 
    bool detail_in(SequenceContainer const& c, sequence_container_tag) const
    {
        std::cout << "sequence overload: ";
        using std::begin; using std::end;
        return in(begin(c), end(c));    
    }
};

int main()
{
    foo f{1};
    int a1[] = { 1, 2, 3};
    int a2[] = { 2, 3, 4};

    std::cout << f.in({1, 2, 3}) << "\n";
    std::cout << f.in({2, 3, 4}) << "\n";

    std::cout << f.in(std::begin(a1), std::end(a1)) << "\n";
    std::cout << f.in(std::begin(a2), std::end(a2)) << "\n";

    std::cout << f.in(a1) << "\n";
    std::cout << f.in(a2) << "\n";

    std::cout << f.in(std::array<int, 3>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::array<int, 3>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::vector<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::vector<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::set<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::unordered_set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::unordered_set<int>{ 2, 3, 4 }) << "\n";    
}

Live Example -对于所有可能的容器- 为两个数字集打印 1 和 0。

std::initializer_list 重载的用例用于对您在调用代码中明确写出小组数字进行成员资格测试。它具有O(N) 的复杂性,但避免了任何堆分配。

对于诸如大型集合的成员资格测试之类的任何繁重任务,您可以将数字存储在一个关联容器中,例如std::set,或其multi_setunordered_set 表亲。这将在存储这些数字时进入堆,但具有O(log N) 甚至O(1) 查找复杂性。

但如果你碰巧只有一个装满数字的序列容器,你也可以把它扔给班级,它会很乐意在O(N)时间为你计算成员资格。

【讨论】:

  • 使用 STL 始终是解决方案。
  • initializer_list 不是 STL 的一部分。
  • 为什么不std::set?这是 STL 的一部分。
  • 我会将foo_in_numbers 移至foo.in(std::initializer_list&lt;T&gt; numbers)。然后你可以写f.in({1,2,3}),IMO 看起来好多了。
【解决方案2】:

使用 STL 有很多方法可以做到这一点。

如果您有非常多的项目并且想要测试您的给定项目是否属于该集合的成员,请使用setunordered_set。它们允许您分别检查log n 和恒定时间的成员资格。

如果您将元素保存在排序数组中,那么binary_search 还将在log n 时间测试成员资格。

对于小型数组,使用find 的线性搜索可能会执行得更快(因为没有分支)。 A linear search might even do 3-8 comparisons in the time it takes the binary search to 'jump around'This blog post 建议在大约 64 个项目处有一个收支平衡点,低于该点线性搜索可能会更快,但这显然取决于 STL 实现、编译器优化和您的架构的分支预测。

【讨论】:

    【解决方案3】:

    如果data确实是整数或枚举类型,可以使用switch

    switch (data) {
      case 1:
      case 2:
      case 2000:
      case 6000:
      case /* whatever other values you want */:
        act_on_the_group();
        break;
      default:
        act_on_not_the_group();
        break;
    }
    

    【讨论】:

      【解决方案4】:

      使用std::initializer_list 的答案很好,但我想再添加一个可能的解决方案,这正是您以类型安全和现代的方式尝试使用 C 可变参数的方法:使用 C++11 可变参数模板

      template<typename... NUMBERS>
      bool any_equal( const foo& f , NUMBERS&&... numbers )
      {
          auto unpacked = { numbers... };
      
          return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
                 != std::end( unpacked );
      };
      

      当然,这只有在传递的所有值都是相同类型时才有效。如果不是初始化列表unpacked,则无法推导或初始化。

      然后:

      bool equals = any_equal( f , 1,2,3,4,5 );
      

      编辑:这是一个are_same 元函数,用于确保传递的所有数字都是同一类型:

      template<typename HEAD , typename... TAIL>
      struct are_same : public and_op<std::is_same<HEAD,TAIL>::value...>
      {};
      

      and_op 执行 n 元逻辑与:

      template<bool HEAD , bool... TAIL>
      struct and_op : public std::integral_constant<bool,HEAD && and_op<TAIL...>::value>
      {};
      
      template<>
      struct and_op<> : public std::true_type
      {};
      

      这使得以简单的方式强制使用相同类型的数字成为可能:

      template<typename... NUMBERS>
      bool any_equal( const foo& f , NUMBERS&&... numbers )
      {
          static_assert( all_same<NUMBERS...>::value , 
                         "ERROR: You should use numbers of the same type" );
      
      
          auto unpacked = { numbers... };
      
          return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
                 != std::end( unpacked );
      };
      

      【讨论】:

      • 难道没有办法表示所有 NUMBERS 都应该具有相同的类型吗? initializer_list 似乎使用了普通开发人员无法访问的构造。
      • @MatthieuM。编写递归函数时,是的,将固定类型放在头上而不是模板上。但是对于这种情况,我们应该编写一个自定义元函数,因为标准库不提供 n 元 std::is_same
      • @MatthieuM。我添加了一个例子
      • 或者干脆绕过同类型限制:coliru.stacked-crooked.com/a/acf1e702c4fb33b3
      【解决方案5】:

      任何优化都将取决于被比较的数字集的属性。

      如果有明确的上限,您可以使用std::bitset。测试成员资格(即对位集进行索引,其行为类似于数组)是 O(1),实际上是一些快速指令。这通常是数百个限制的最佳解决方案,尽管根据应用程序数百万个可能是实际的。

      【讨论】:

      • 是的,如果进行多次比较,这是个好主意。仍然需要迭代一次,但只有一次。
      • @MooingDuck 这是一个表格查找。我不会称之为迭代。
      • 那么,您打算如何填充表格?您遍历数字并在查找中设置该位,然后您无需再次遍历数字。
      • @MooingDuck 我假设该集合是一个编译时常量。问题中并未真正讨论生成集合,但原始问题陈述使用数字文字。 (现在,如何生成一个大的位集是这​​个答案的一个相当大的遗漏。IIRC,C++14 几乎允许您编写一个 for 循环并仍然获得静态初始化,如果仔细完成的话。 constexpr bitset 成员的合规扩展可能会发生这种情况。)
      【解决方案6】:

      它不漂亮,但这应该可以:

      class foo {
          bool equals(int a) { return a == data; }
          bool equals(int a, int b) { return (a == data) || (b == data); }
          bool equals(int a, int b, int c) {...}     
          bool equals(int a, int b, int c, int d) {...} 
      private:
          int data; 
      }
      

      等等。这将为您提供您所追求的确切语法。但是,如果您追求完全可变数量的参数,那么向量或 std::initializer 列表可能是要走的路:

      见:http://en.cppreference.com/w/cpp/utility/initializer_list

      这个例子展示了它的实际效果:

      #include <assert.h>
      #include <initializer_list>
      
      class foo {
      public:
              foo(int d) : data(d) {}
              bool equals_one_of(std::initializer_list<int> options) {
                      for (auto o: options) {
                              if (o == data) return true;
                      }
                      return false;
              }
      private:
              int data;
      };
      
      int main() {
              foo f(10);
              assert(f.equals_one_of({1,3,5,7,8,10,14}));
              assert(!f.equals_one_of({3,6,14}));
              return 0;
      }
      

      【讨论】:

      • 提示:使用std::find
      • @MatthieuM。同意。我通常不在 STL 领域,所以这很有趣。我倾向于这个非常通用的解决方案: template bool contains(std::initializer_list list, S value) { return std::find(list.begin(), list.end( ), 值) != list.end(); }
      • 您实际上可以得到更通用的方法:不要使用专用容器 :) bool contains(C const&amp; c, V const&amp; v) { using std::begin; using std::end; return std::find(begin(c), end(c), v) != end(c); },尽管如果通过容器以外的其他东西,诊断会不太愉快...
      【解决方案7】:

      有人有更好的主意吗?感谢分享!

      有一个标准的算法:

      using std::vector; // & std::begin && std::end
      
      // if(data is equal to one of these(1,2,3,4,5,6))
      /* maybe static const */vector<int> criteria{ 1, 2, 3, 4, 5, 6 };
      return end(criteria) != std::find(begin(criteria), end(criteria), data);
      

      编辑:(都在一个地方):

      bool is_equal_to_one_of_these(int data, const std::vector<int>& criteria)
      {
          using std::end; using std::begin; using std::find;
          return end(criteria) != find(begin(criteria), end(criteria), data);
      }
      
      auto data_matches = is_equal_to_one_of_these(data, {1, 2, 3, 4, 5, 6});
      

      编辑:

      我更喜欢向量的接口,而不是初始化列表,因为它更强大:

      std:vector<int> v = make_candidate_values_elsewhere();
      auto data_matches = is_equal_to_one_of_these(data, v);
      

      接口(通过使用向量)不限制您定义值,您可以在其中调用is_equal_to_one_of_these

      【讨论】:

      • static const vector 本质上与std::initializer_list 相同。
      • @Potatoswatter:不完全是。看到std::initializer_list 存储在只读内存中并且优化器可以充分访问我不会感到惊讶,因此不断传播和内联可能会让优化器意识到1 in {1, 2, 3, 4} 总是正确的(例如),而我会更多由于间接级别,如果它与 vector 一起使用会更令人惊讶。
      • @MatthieuM。我只说功能上。是的,如果initializer_list 效率不高,它就没有存在的理由!
      • @utnapistim 针对特定容器的编码算法是个坏主意。如果集合位于 std::deque、数组(std 或 C)或 std::set 中怎么办?最好使用迭代器。
      【解决方案8】:

      set 是一个不错的选择,但如果你真的想自己动手,initializer_list 很方便:

      bool is_in( int val, initializer_list<int> lst )
      {
          for( auto i : lst )
              if( i == val ) return true;
          return false;
      }
      

      使用很简单:

      is_in( x, { 3, 5, 7 } ) ;
      

      你是 O(n),设置/无序更快

      【讨论】:

      • 只有当这组数字已经作为一组可用时才会更快。
      【解决方案9】:

      我建议使用像 std::vector 这样的标准容器,但这仍然意味着线性复杂性,最坏情况下的运行时间为 O(N)

      class Foo{
      public:
          int data;
          bool is_equal_to_one_of_these(const std::vector<int>& arguments){
              bool matched = false;
              for(int arg : arguments){ //if you are not using C++11: for(int i = 0; i < arguments.size(); i++){
                  if( arg == data ){ //if you are not using C++11: if(arguments[i] == data){
                      matched = true;
                  }
              }
              return matched;
          }
      };
      
      std::vector<int> exampleRange{ {1,2,3,4,5} };
      Foo f;
      f.data = 3;
      std::cout << f.is_equal_to_one_of_these(exampleRange); // prints "true"
      

      【讨论】:

      • 所有控制路径不返回值
      • 注意:您可以在循环中简单地设置matched,而不是设置return true;;这样,您可以将其短路并避免在第一个项目匹配时循环遍历所有集合。还有……return std::find(arguments.begin(), arguments.end(), data) != arguments.end();
      • @MatthieuM。你是对的,但是std::find 已经有足够的答案了;)
      【解决方案10】:

      如果data、num1、..num6在0到31之间,那么可以使用

      int match = ((1<<num1) | (1<<num2) | ... | (1 << num6));
      if( ( (1 << data) & match ) != 0 ) ...
      

      如果 num1 到 num6 是常量,编译器将在编译时计算匹配。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-08
        • 1970-01-01
        • 2012-09-25
        • 1970-01-01
        • 2010-10-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多