我最近一直在将一个Python应用程序移植到C ++中,但现在我不知道如何移植特定的函数。这是相应的Python代码:
def foo(a, b): # Where `a' is a list of strings, as is `b'
for x in a:
if not x in b:
return False
return True
我希望有类似的功能:
bool
foo (char* a[], char* b[])
{
// ...
}
最简单的方法是什么?我尝试过使用STL算法,但似乎无法使它们工作。例如,我目前有这个(使用glib类型):
gboolean
foo (gchar* a[], gchar* b[])
{
gboolean result;
std::sort (a, (a + (sizeof (a) / sizeof (*a))); // The second argument corresponds to the size of the array.
std::sort (b, (b + (sizeof (b) / sizeof (*b)));
result = std::includes (b, (b + (sizeof (b) / sizeof (*b))),
a, (a + (sizeof (a) / sizeof (*a))));
return result;
}
我非常愿意使用C ++ 11的功能。
答案 0 :(得分:3)
我只是想对其他人强调的内容添加一些评论,并为您想要的内容提供更好的算法。
请勿在此处使用指针。使用指针不会使它成为c ++,它会使编码变得糟糕。如果你有一本以这种方式教你c ++的书,就把它扔掉。仅仅因为某种语言具有某种功能,并不意味着在可以的任何地方使用它都是正确的。如果您想成为一名专业程序员,您需要学习使用语言的相应部分来执行任何特定操作。当您需要数据结构时,请使用适合您的活动的数据结构。指针不是数据结构,它们是当您需要具有状态生命周期的对象时使用的引用类型 - 即,在一个异步事件上创建对象并在另一个异步事件上销毁时。如果一个对象的生命周期没有任何异步等待,它可以被建模为一个堆栈对象,应该是。指针永远不会暴露在应用程序代码中而不会被包装在对象中,因为标准操作(如new)会抛出异常,而指针不会自行清理。换句话说,指针应始终仅在类内使用,并且仅在必要时使用动态创建的对象响应类的外部事件(可能是异步的)。
请勿在此处使用数组。数组是在编译时已知的堆栈生命周期的简单同构集合数据类型。它们不适合迭代。如果您需要一个允许迭代的对象,则有一些类型具有内置功能。但是,使用数组执行此操作意味着您要跟踪数组外部的大小变量。这也意味着你强制执行数组的外部,迭代不会在每次迭代时使用新形成的条件延伸到最后一个元素之外(注意这不仅仅是管理大小 - 它管理一个不变量,你在第一名)。你没有重用标准算法,正在与衰变指针作斗争,并且通常会制造脆弱的代码。只有在封装和使用数组的情况下,数组才会有用,只需要随机访问一个简单类型,而不需要迭代。
不要在此处对矢量进行排序。这个很奇怪,因为它不是原始问题的良好翻译,我不知道它来自哪里。不要尽早优化,但也不要通过选择错误的算法来提前估计。这里的要求是在另一个字符串集合中查找每个字符串。排序后的矢量是一个不变量(所以,再想想需要封装的东西) - 你可以使用库中的现有类,比如boost或roll你自己的。但是,平均来说要好一点就是使用哈希表。使用摊销的O(N)查找(N是查找字符串的大小 - 记住它是分摊的O(1)哈希比较数,对于字符串这个O(N)),一种自然的第一种方式来翻译“查找一个字符串“是让unordered_set<string>
成为算法中的b
。这会将算法的复杂性从O(NM log P)改变(现在N为a
中字符串的平均大小,M为集合a
的大小,P为集合{{1}的大小,)到O(NM)。如果集合b
变大,这可以节省很多。
换句话说
b
注意,您现在可以将常量传递给函数。如果你在考虑使用它们的情况下构建你的系列,那么你通常可以节省额外的费用。
这种反应的关键在于你真的应该养成编写像发布的代码一样的习惯。遗憾的是,有一些真正(非常)糟糕的书籍用这样的字符串来教授编码,这真是一种耻辱,因为没有必要让代码看起来那么可怕。它促进了c ++是一种强硬语言的想法,当它有一些非常好的抽象,比其他语言中的许多标准习语更容易和更好的性能。一本好书的例子,教你如何预先使用语言的力量,所以你不养成坏习惯,是Koenig和Moo的“Accelerated C ++”。
但是,您应该始终考虑这里提出的观点,与您使用的语言无关。您永远不应该尝试在封装之外强制执行不变量 - 这是面向对象设计中重用节省的最大来源。您应该始终选择适合其实际使用的数据结构。并且只要有可能,请使用您正在使用的语言的强大功能,以避免重新发明轮子。 C ++已经内置了字符串管理和比较,它已经具有高效的查找数据结构。如果你稍微考虑一下这个问题,它有能力完成你可以简单编码的许多任务。
答案 1 :(得分:2)
你的第一个问题与在C ++中处理(不)数组的方式有关。阵列存在一种非常脆弱的阴影存在,如果你以一种有趣的方式看待它们,它们就会变成指针。您的函数没有像您期望的那样采用两个指向数组的指针。它需要两个指针指针。
换句话说,您将丢失有关阵列 size 的所有信息。 sizeof(a)
没有给出数组的大小。它为您提供指针指针的大小。
所以你有两个选择:快速和脏的ad-hoc解决方案是明确传递数组大小:
gboolean foo (gchar** a, int a_size, gchar** b, int b_size)
或者,更好,你可以使用向量而不是数组:
gboolean foo (const std::vector<gchar*>& a, const std::vector<gchar*>& b)
向量是动态大小的数组,因此,它们知道它们的大小。 a.size()
将为您提供向量中元素的数量。但它们还有两个方便的成员函数begin()
和end()
,旨在使用标准库算法。
所以,要对矢量进行排序:
std::sort(a.begin(), a.end());
同样适用于std::includes
。
你的第二个问题是你不是在字符串上操作,而是在字符指针上操作。换句话说,std::sort
将按指针地址排序,而不是按字符串内容排序。
同样,您有两种选择:
如果你坚持使用char指针而不是字符串,你可以为std::sort
指定一个自定义比较器(使用lambda,因为你在评论中提到你可以使用它们)
std::sort(a.begin(), a.end(), [](gchar* lhs, gchar* rhs) { return strcmp(lhs, rhs) < 0; });
同样,std::includes
采用可选的第五个参数来比较元素。那里可以使用相同的lambda。
或者,您只需使用std::string
而不是您的char指针。然后默认比较器工作:
gboolean
foo (const std::vector<std::string>& a, const std::vector<std::string>& b)
{
gboolean result;
std::sort (a.begin(), a.end());
std::sort (b.begin(), b.end());
result = std::includes (b.begin(), b.end(),
a.begin(), a.end());
return result;
}
更简单,更清洁,更安全。
答案 2 :(得分:1)
C ++版本中的排序不起作用,因为它正在对指针值进行排序(将它们与std::less
进行比较,就像对待其他所有内容一样)。您可以通过提供适当的比较仿函数来解决这个问题。但是为什么不在C ++代码中实际使用std::string
? Python字符串是真正的字符串,因此将它们作为真正的字符串移植是有意义的。
答案 3 :(得分:1)
在您的示例代码段中,使用std::includes
毫无意义,因为它会使用operator<
来比较您的元素。除非您在两个数组中存储相同的指针,否则操作不会产生您要查找的结果。
比较地址与比较 c-style-strings 的真实内容并不是一回事。
您还必须向std::sort
提供必要的比较器,最好是std::strcmp
(包含在仿函数中)。
它目前遇到与使用std::includes
相同的问题,它正在比较地址而不是 c-style-strings 的内容。
使用std::string
和std::vector
s可以避免整个“问题”。
示例摘录
#include <iostream>
#include <algorithm>
#include <cstring>
typedef char gchar;
gchar const * a1[5] = {
"hello", "world", "stack", "overflow", "internet"
};
gchar const * a2[] = {
"world", "internet", "hello"
};
...
int
main (int argc, char *argv[])
{
auto Sorter = [](gchar const* lhs, gchar const* rhs) {
return std::strcmp (lhs, rhs) < 0 ? true : false;
};
std::sort (a1, a1 + 5, Sorter);
std::sort (a2, a2 + 3, Sorter);
if (std::includes (a1, a1 + 5, a2, a2 + 3, Sorter)) {
std::cerr << "all elements in a2 was found in a1!\n";
} else {
std::cerr << "all elements in a2 wasn't found in a1!\n";
}
}
<强>输出强>
all elements in a2 was found in a1!
答案 4 :(得分:1)
python版本的天真转录将是:
bool foo(std::vector<std::string> const &a,std::vector<std::string> const &b) {
for(auto &s : a)
if(end(b) == std::find(begin(b),end(b),s))
return false;
return true;
}
事实证明,对输入进行排序非常慢。 (面对重复的元素,这是错误的。)即使是天真的功能通常要快得多。再次表明,过早优化是万恶之源。
这是一个unordered_set版本,通常比天真版本快一些(或者是我测试的值/用法模式):
bool foo(std::vector<std::string> const& a,std::unordered_set<std::string> const& b) {
for(auto &s:a)
if(b.count(s) < 1)
return false;
return true;
}
另一方面,如果向量已经排序且b
相对较小(对我来说小于约200k),那么std::includes
非常快。因此,如果您关心速度,您只需针对您实际处理的数据和使用模式进行优化。