C ++ - 检查一个字符串数组是否包含另一个字符串

时间:2012-03-20 20:51:51

标签: c++ multidimensional-array c++11 iteration

我最近一直在将一个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的功能。

5 个答案:

答案 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::stringstd::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非常快。因此,如果您关心速度,您只需针对您实际处理的数据和使用模式进行优化。