簡體   English   中英

C ++-檢查一個字符串數組是否包含另一個字符串的所有元素

[英]C++ - Check if One Array of Strings Contains All Elements of Another

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

我將對其他人強調的內容添加一些評論,並針對您想要的內容給出更好的算法。

不要在這里使用指針 使用指針不能使它成為c ++,它會使編碼變得很糟糕。 如果您有一本書以這種方式教了您c ++,請將其丟棄。 僅僅因為一種語言具有某種功能,並不意味着在任何地方都可以使用它。 如果您想成為專業的程序員,則需要學習將語言的相應部分用於任何給定的操作。 需要數據結構時,請使用適合您的活動的數據結構。 指針不是數據結構,它們是當您需要具有狀態生存期的對象時(即,在一個異步事件上創建對象而在另一個異步事件上銷毀對象時)使用的引用類型。 如果對象在沒有任何異步等待的情況下生存,則可以將其建模為堆棧對象,並且應該如此。 如果指針不包裝在對象中,則決不應將其暴露給應用程序代碼,因為標准操作(如new)會引發異常,並且指針不會自行清理。 換句話說,指針應始終僅在類內部使用,並且僅在必要時使用動態創建的對象對類的外部事件做出響應(可能是異步的)。

不要在這里使用數組 數組是在編譯時已知大小的堆棧生存期的簡單同質集合數據類型。 它們不用於迭代。 如果需要允許迭代的對象,則可以為此內置一些類型的工具。 但是,使用數組來執行此操作意味着您要跟蹤數組外部的size變量。 這也意味着您要在數組外部強制迭代將不會在每次迭代時使用新形成的條件擴展到最后一個元素(請注意,這與僅管理大小是不同的-它在管理不變式,這是因為您在第一名)。 您不必重用標准算法,需要克服指針衰減,並且通常需要編寫脆弱的代碼。 數組只有在被封裝和使用的情況下(再次)才有用,其中唯一的要求是隨機訪問簡單類型,而無需迭代。

不要在此對向量排序 這很奇怪,因為它不是您最初提出的問題的很好翻譯,而且我不確定它是從哪里來的。 不要提早優化,也不要通過選擇錯誤的算法提早進行悲觀。 這里的要求是在另一個字符串集合中查找每個字符串。 排序后的向量是不變的(因此,再次考慮一下需要封裝的東西)-您可以使用來自諸如boost或roll自己的庫的現有類。 但是,平均而言,使用哈希表要好一些。 使用攤銷O(N)查找(N為查找字符串的大小-請記住,攤銷O(1)的哈希比較數,對於字符串為O(N)),這是翻譯“查找a字符串”是為了使unordered_set<string>成為算法中的b 這將算法的復雜性從O(NM log P)(N現在是a中字符串的平均大小,M集合a的大小和P集合b的大小)更改為O(NM)。 如果集合b變大,則可以節省很多。

換一種說法

gboolean foo(vector<string> const& a, unordered_set<string> const& b)

注意,您現在可以將常量傳遞給函數。 如果您在收藏時考慮到用途,那么通常會節省大量潛在成本。

這種回應的意義在於,您實際上絕對不應該養成像發布那樣編寫代碼的習慣。 可惜的是,那里確實有一些真正(真的)糟糕的書指導着這樣的字符串編碼,這實在是可恥的,因為根本不需要讓代碼看起來那么恐怖。 當c ++具有一些非常好的抽象語言,並且比其他語言中的許多標准慣用法更容易執行並具有更好的性能時,它就會樹立一種觀念。 Koenig和Moo撰寫的“ Accelerated C ++”是一本好的書的例子,該書可以教您如何預先使用語言的功能,從而避免養成不良習慣。

而且,您應該始終考慮這里提出的觀點,而與所使用的語言無關。 您永遠不要嘗試在封裝之外強制執行不變式-這是在面向對象設計中發現的最大的重用節省來源。 而且,您應始終選擇適合其實際使用的數據結構。 並盡可能利用您所用語言的力量來發揮自己的優勢,以免您不得不重新發明輪子。 C ++已經內置了字符串管理和比較功能,它已經具有高效的查找數據結構。 如果您稍加思考,便可以執行許多可以簡單地描述的任務。

您的第一個問題與在C ++中處理(不)處理數組的方式有關。 數組存在一種非常脆弱的陰影,如果您以一種有趣的方式看待它們,它們將被轉換為指針。 您的函數不會像您期望的那樣使用兩個指向數組的指針。 它需要兩個指針。

換句話說,您將丟失有關數組大小的所有信息。 sizeof(a)不能提供數組的大小。 它為您提供了指針的大小。

因此,您有兩種選擇:快速而又骯臟的臨時解決方案是顯式傳遞數組大小:

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

第二個問題是您不對字符串進行操作,而是對char指針進行操作。 換句話說, 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;
}

更簡單,更清潔,更安全。

C ++版本中的排序不起作用,因為它正在對指針值進行排序(將它們與std::less進行比較,就像其他所有操作一樣)。 您可以通過提供適當的比較函子來解決此問題。 但是,為什么不真正在C ++代碼中使用std::string Python字符串是真實字符串,因此將它們移植為真實字符串是有意義的。

在您的示例代碼片段中,對std::includes使用毫無意義,因為它將使用operator<來比較您的元素。 除非在兩個數組中都存儲相同的指針,否則該操作將不會產生您想要的結果。

比較地址和比較c樣式字符串的真實內容不是同一回事。


您還必須為std::sort提供必要的比較器,最好是std::strcmp (包裝在函子中 )。

它目前遇到與使用std::includes相同的問題,它在比較地址而不是c-style-strings的內容


通過使用std::stringstd::vector可以避免整個“問題”。


示例片段

#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!

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非常快。 因此,如果您關心速度,則只需要針對實際處理的數據和使用模式進行優化即可。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM