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

What is the C++ equivalent of Python's "in" operator?

本文关键字:是什么 等价物 运算符 in Python C++      更新时间:2023-10-16

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

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

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

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

  • https://wiki.python.org/moin/TimeComplexity

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

  • http://en.cppreference.com/w/cpp/algorithm/find
  • 检查元素是否在列表中(包含)
  • 检查是否在数组 c++ 中找到元素
  • http://en.cppreference.com/w/cpp/algorithm/binary_search

标准库提供的关联容器(std::setstd::unordered_setstd::map、...)为此提供了成员函数find()count()contains()(C++20)。这些将比线性搜索(即对数或恒定时间)表现更好,具体取决于您是选择了有序还是无序替代方案。更喜欢这些功能中的哪一个在很大程度上取决于您之后想用这些信息实现什么,但也取决于个人喜好。(有关详细信息和示例,请查找文档。

  • 如何检查元素是否在 std::set 中?
  • 如何在不插入的情况下检查 std::map 是否包含键?
  • https://en.wikipedia.org/wiki/Associative_containers
  • http://en.cppreference.com/w/cpp/container

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

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

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

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

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

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

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

因此,我们可以自己写一些东西来做检查。最简洁的是利用 C++17 的if constexpr并使用类似 Yakkcan_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;两步,以便处理所有标准容器、原始数组和任何使用提供/适应的容器。

这为您提供了一个中缀*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 << "yesn";
if (7 *in* v)
std::cout << "non";
std::map<std::string, std::string, std::less<>> m{
{"hello", "world"}
};

if ("hello" *in* m)
std::cout << "hello worldn";

活生生的例子。

C++14,但主要是为了enable_if_t.

这到底是怎么回事呢?

好吧,can_apply是一些代码,可以让我编写can_dot_find,它检测(在编译时)container.find(x)是否是有效的表达式。

这让我可以调度搜索代码以使用成员查找(如果存在)。 如果不存在,则改用使用std::find的线性搜索。

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

ADL(参数相关查找)可扩展性(第三方扩展能力)是为什么我们有 三个不同的函数find,两个在帮助程序命名空间中,一个在notstd中。 您打算调用notstd::find.

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

<小时 />

里拉;博士

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

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

这为您提供了在各种std容器上非常好的性能。

它唯一不支持的是

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

因为运算符(=除外)无法推断出我相信的初始值设定项列表。 你可以得到

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

去工作。

我想人们可能会利用这个线程并创建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上的小用法示例。

您可以使用<algorithm>中的std::find,但这仅适用于以下数据类型:std::mapstd::vector(等)。

另请注意,这将返回第一个元素的迭代器,该元素等于您传递的值,这与 Python 中返回布尔值的in运算符不同。

我认为python中"in"运算符的一个很好的功能是它可以与不同的数据类型(字符串v/s字符串,数字v/s列表等)一起使用。

我正在开发一个用于在C++中使用python结构的库。它包括"in"和"not_in"运算符。

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

  • 在字符串中搜索字符串
  • 在矢量和地图中搜索元素

它的工作原理是为一个函数定义几个重载: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