什么是Python的“in”运算符的C ++等价物?

时间:2017-06-19 05:41:02

标签: c++ arrays

检查元素是否包含在数组/列表中的C ++方法是什么,类似于in运算符在Python中的作用?

if x in arr:
    print "found"
else
    print "not found"

与Python的in运算符相比,C ++等价物的时间复杂度如何?

7 个答案:

答案 0 :(得分:54)

Python in运算符的时间复杂度取决于实际调用它的数据结构。当您将它与列表一起使用时,复杂性是线性的(正如人们所期望的那样,没有索引的未排序数组)。当您使用它来查找集合成员资格或存在字典密钥时,复杂性平均是恒定的(正如人们对基于哈希表的实现所期望的那样):

在C ++中,您可以使用std::find来确定某个项目是否包含在std::vector中。复杂度被认为是线性的(正如人们所期望的那样,没有索引的未排序数组)。如果确保向量已排序,则还可以使用std::binary_search在对数时间内实现相同的目标。

标准库(std::setstd::unordered_setstd::map,...)提供的关联容器为此提供了成员函数find(),它将表现更好比线性搜索,即对数或恒定时间,取决于你是选择了有序还是无序的替代。

如果你愿意,你可以使用一些模板魔术来编写一个包装器函数来为手头的容器选择正确的方法,例如,如this answer中所示。

答案 1 :(得分:14)

您可以通过两种方式解决此问题:

您可以使用<algorithm>中的std::find

auto it = std::find(container.begin(), container.end(), value);
if (it != container.end())
    return it;  

或者您可以使用远程循环遍历容器中的每个元素:

for(const auto& it : container)
{
    if(it == value)
        return it;
} 

答案 2 :(得分:11)

Python为in做了不同的事情,具体取决于它是什么类型的容器。在C ++中,您需要相同的机制。标准容器的经验法则是,如果它们提供find(),它将是比std::find()更好的算法(例如find() std::unordered_map是O(1) ,但std::find()总是O(N))。

所以我们可以写一些东西来检查自己。最简洁的是利用C ++ 17的if constexpr并使用类似Yakk的can_apply

template <class C, class K>
using find_t = decltype(std::declval<C const&>().find(std::declval<K const&>()));

template <class Container, class Key>
bool in(Container const& c, Key const& key) {
    if constexpr (can_apply<find_t, Container, Key>{}) {
        // the specialized case
        return c.find(key) != c.end();
    } else {
        // the general case 
        using std::begin; using std::end;
        return std::find(begin(c), end(c), key) != end(c);
    }
}

在C ++ 11中,我们可以利用表达式SFINAE:

namespace details {
    // the specialized case
    template <class C, class K>
    auto in_impl(C const& c, K const& key, int )
            -> decltype(c.find(key), true) {
        return c.find(key) != c.end();
    }

    // the general case
    template <class C, class K>
    bool in_impl(C const& c, K const& key, ...) {
        using std::begin; using std::end;
        return std::find(begin(c), end(c), key) != end(c);
    }
}

template <class Container, class Key>
bool in(Container const& c, Key const& key) {
    return details::in_impl(c, key, 0);
}

请注意,在这两种情况下,我们都有using std::begin; using std::end;两步,以便处理所有标准容器,原始数组和任何使用提供/适应的容器。

答案 3 :(得分:9)

我想有人可能会使用this线程并创建in函数的自定义版本。

主要思想是使用SFINAE(替换失败不是错误)来区分关联容器(具有 key_type 成员)与序列容器(没有 key_type) 成员)。

这是一个可能的实现:

namespace detail
{
    template<typename, typename = void>
    struct is_associative : std::false_type {};

    template<typename T>
    struct is_associative<T,
        std::enable_if_t<sizeof(typename T::key_type) != 0>> : std::true_type {};

    template<typename C, typename T>
    auto in(const C& container, const T& value) ->
        std::enable_if_t<is_associative<C>::value, bool>
    {
        using std::cend;

        return container.find(value) != cend(container);
    }

    template<typename C, typename T>
    auto in(const C& container, const T& value) ->
        std::enable_if_t<!is_associative<C>::value, bool>
    {
        using std::cbegin;
        using std::cend;

        return std::find(cbegin(container), cend(container), value) != cend(container);
    }

}

template<typename C, typename T>
auto in(const C& container, const T& value)
{
    return detail::in(container, value);
}

WANDBOX上的小用法示例。

答案 4 :(得分:6)

这会为您提供中缀*in*运算符:

namespace notstd {
  namespace ca_helper {
    template<template<class...>class, class, class...>
    struct can_apply:std::false_type{};
    template<class...>struct voider{using type=void;};
    template<class...Ts>using void_t=typename voider<Ts...>::type;

    template<template<class...>class Z, class...Ts>
    struct can_apply<Z,void_t<Z<Ts...>>, Ts...>:std::true_type{};
  }
  template<template<class...>class Z, class...Ts>
  using can_apply = ca_helper::can_apply<Z,void,Ts...>;

  namespace find_helper {
    template<class C, class T>
    using dot_find_r = decltype(std::declval<C>().find(std::declval<T>()));
    template<class C, class T>
    using can_dot_find = can_apply< dot_find_r, C, T >;
    template<class C, class T>
    constexpr std::enable_if_t<can_dot_find<C&, T>{},bool>
    find( C&& c, T&& t ) {
      using std::end;
      return c.find(std::forward<T>(t)) != end(c);
    }
    template<class C, class T>
    constexpr std::enable_if_t<!can_dot_find<C&, T>{},bool>
    find( C&& c, T&& t ) {
      using std::begin; using std::end;
      return std::find(begin(c), end(c), std::forward<T>(t)) != end(c);
    }
    template<class C, class T>
    constexpr bool finder( C&& c, T&& t ) {
      return find( std::forward<C>(c), std::forward<T>(t) );
    }
  }
  template<class C, class T>
  constexpr bool find( C&& c, T&& t ) {
    return find_helper::finder( std::forward<C>(c), std::forward<T>(t) );
  }
  struct finder_t {
    template<class C, class T>
    constexpr bool operator()(C&& c, T&& t)const {
      return find( std::forward<C>(c), std::forward<T>(t) );
    }
    constexpr finder_t() {}
  };
  constexpr finder_t finder{};
  namespace named_operator {
    template<class D>struct make_operator{make_operator(){}};

    template<class T, char, class O> struct half_apply { T&& lhs; };

    template<class Lhs, class Op>
    half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
      return {std::forward<Lhs>(lhs)};
    }

    template<class Lhs, class Op, class Rhs>
    auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
    -> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
    {
      return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
    }
  }
  namespace in_helper {
    struct in_t:notstd::named_operator::make_operator<in_t> {};
    template<class T, class C>
    bool named_invoke( T&& t, in_t, C&& c ) {
      return ::notstd::find(std::forward<C>(c), std::forward<T>(t));
    }
  }
  in_helper::in_t in;
}

在扁平容器上,如矢量数组或字符串,它是O(n)。

在关联排序容器上,如std::mapstd::set,它是O(lg(n))。

在无序的关联容器上,如std::unordered_set,它是O(1)。

测试代码:

std::vector<int> v{1,2,3};
if (1 *in* v)
    std::cout << "yes\n";
if (7 *in* v)
    std::cout << "no\n";
std::map<std::string, std::string, std::less<>> m{
    {"hello", "world"}
};

if ("hello" *in* m)
    std::cout << "hello world\n";

Live example

C ++ 14,但主要针对enable_if_t

那么这里发生了什么?

嗯,can_apply是一些允许我编写can_dot_find的代码,它会检测(在编译时)container.find(x)是否为有效表达式。

这使我可以调度搜索代码以使用member-find(如果存在)。如果它不存在,则使用std::find进行线性搜索。

这有点谎言。如果在容器的命名空间中定义了一个自由函数find(c, t),它将使用它而不是上述任何一个。但这就是我的想象力(它允许你使用*in*支持扩展第三方容器。)

ADL(参数依赖查找)extensibity(第三方扩展能力)是我们有三个不同的函数find,两个在辅助命名空间中,一个在notstd中的原因。您打算致电notstd::find

接下来,我们想要一个类似python的in,什么比infix运算符更像python?要在C ++中执行此操作,您需要将运算符名称包装在其他运算符中。我选择了*,因此我们得到了一个名为运算符的中缀*in*

TL; DR

您执行using notstd::in;导入命名运算符in

之后,t *in* c首先检查find(t,c)是否有效。如果没有,它会检查c.find(t)是否有效。如果失败,则会使用c std::beginstd::endstd::find进行线性搜索。

这使您在各种std容器上获得了非常好的性能。

它唯一不支持的是

if (7 *in* {1,2,3})

因为运算符(=除外)不能推断我认为的初始化列表。你可以得到

if (7 *in* il(1,2,3))

工作。

答案 5 :(得分:1)

你可以使用std :: find from&lt;算法&gt;。但这仅适用于数据类型,如std :: map,std :: vector等。 另请注意,这将返回迭代器,找到第一个与您传递的值相等的元素,而不像python中的IN运算符返回true / false。

答案 6 :(得分:0)

我认为 python 中“in”运算符的一个很好的特性是它可以用于不同的数据类型(字符串v/s 字符串、数字v/s 列表等)。

我正在开发一个玩具库,用于在 C++ 中使用 python 结构。它包含一个“in”运算符。

它基于用于实现上一个答案中发布的 in 运算符的相同技术,其中实现了 make_operator。但是,它被扩展以处理更多情况:

  • 在字符串中搜索字符串
  • 在向量中搜索元素
  • 实施“not_in”运算符

它的工作原理是为一个函数定义几个重载:bool in__(T1 &v1, T2 &v2),其中 T1 和 T2 考虑不同的可能类型的对象。此外,还定义了函数的重载: bool not_in__(T1 &v1, T2 &v2)。然后,操作符“in”和“not_in”调用这些函数来工作。

这种方法的主要问题是编译错误非常冗长且无信息......

玩具实现位于此存储库中:

https://github.com/ploncomi/python_like_cpp