簡體   English   中英

在 C++ 中獲取所有長度的 2D 數組元素的所有組合

[英]Get all combinations of elements of all lengths of 2D array in C++

我有一些行和列的二維字符數組,如unsigned char arr[rows][columns]

例如,讓我們假設行 = 10,列 = 10 並且數組填充如下:

   0 1 2 3 4 5 6 7 8  

0: l m n

1: v g h k l Z a b d

2: M q r u v g h k l 

3: M Q R Z a b d

4: M Q R d

5: d

不能保證所有行和列都被填滿,未填滿的空格會被忽略。

我想構建長度為 1 到最大行的所有可能的“單詞”,因此在這種情況下,所有可能的字符組合的長度為 1 到 6,例如:

output:
len : 1
l
m
n
len 2:
lv
lg
lh
...
mv
mg
mn
...
len 3:
lvM
lvq
lvr
...

我試圖實現我的另一個想法:

另一個觀察結果:對於 len == 2,您必須使用 len == 1 的所有輸出,並將每個輸出與第二行的所有字符組合。 對於 len == 3,您必須使用 len == 2 的所有輸出,並將每個輸出與第 3 行的所有字符組合。 等等。

這就是我得到的:

#include <iostream>
#include <string>
#include <vector>

int main()
{
  std::vector<std::string> input = {
    "lmn",
    "vghk",
    "Mqr"
  };
  std::cout << "output:\n";
  // gather all combinations
  std::vector<std::string> output;
  // first row
  std::cout << "len : 1\n";
  const std::string& row0 = input.front();
  for (char c : row0) {
    output.emplace_back(1, c);
    std::cout << c << '\n';
  }
  // other rows
  for (size_t i = 1, n = input.size(); i < n; ++i) {
    std::cout << "len : " << i + 1 << '\n';
    const std::string& rowI = input[i];
    const std::vector<std::string> outputPrev = std::move(output);
    output.clear();
    for (const std::string& textPrev : outputPrev) {
      for (char c : rowI) {
        output.emplace_back(textPrev + c);
        std::cout << output.back() << '\n';
      }
    }
  }
}

輸出:

output:
len : 1
l
m
n
len : 2
lv
lg
lh
lk
mv
mg
mh
mk
nv
ng
nh
nk
len : 3
lvM
lvq
lvr
lgM
lgq
lgr
lhM
lhq
lhr
lkM
lkq
lkr
mvM
mvq
mvr
mgM
mgq
mgr
mhM
mhq
mhr
mkM
mkq
mkr
nvM
nvq
nvr
ngM
ngq
ngr
nhM
nhq
nhr
nkM
nkq
nkr

在coliru上進行現場演示

這是該解決方案的遞歸版本——基於以下事實的遞歸:長度n的組合只是行中的每個項目連接到行中所有項目的長度為n-1的所有組合。

請注意,下面的n是要包含的最后一行的從零開始的索引,因此如果您想要所有 6 個字符組合,這意味着n等於 5。

#include <vector>
#include <string>
#include <iostream>

std::vector<std::string> all_combinations(const std::vector< std::vector <std::string>>& rows, int n) {
    if (n == 0)
        return rows[0];
    auto n_minus_one_combos = all_combinations(rows, n - 1);
    const auto& row = rows[n];
    std::vector<std::string> n_combos;
    n_combos.reserve(n_minus_one_combos.size() * row.size());
    for (const auto& ch : row) {
        for (const auto& n_minus_one_combo : n_minus_one_combos) {
            n_combos.push_back(n_minus_one_combo + ch);
        }
    }
    return n_combos;
}

void main() {
    std::vector< std::vector <std::string>> rows = {
        {"l", "m", "n"},
        {"v", "g", "h", "k", "l", "Z", "a", "b", "d"},
        {"M","q","r","u","v", "g", "h", "k", "l"},
        {"M","Q","R","Z","a","b","d"},
        {"M","Q","R","d"},
        {"d"}
    };

    for (const auto& combo : all_combinations(rows, 5)) {
        std::cout << combo << "\n";
    }
}

通常,一個好的解決方案的方法是

  • 首先分析需求和
  • 然后了解數學背景,
  • 做適當的設計,
  • 使用足夠的 C++ 容器和元素
  • 最后,編寫代碼

經過分析需求,我們發現數學背景是構建2個集合的笛卡爾積。

集合中的元素是std::string 我們從只有一個字符的字符串開始,然后構建笛卡爾積。 結果是一個包含 2 個字符的字符串,包含單個字符的所有可能組合。

對於此任務,構建笛卡爾積非常簡單。 它只是 2 個元素的字符串連接。 使用std::string我們可以簡單地使用 '+' 運算符進行字符串連接。

因此,在 C++ 中,我們可以使用 2 個嵌套的 for 循環,如下所示:

for (const Element a : elements) 
    for (const Element b : B.elements)
         cartesianProduct.elements.insert( a + b );

作為我們元素的容器,我們可以使用 STL 中現有的“集合”之一。

  1. std::set具有唯一元素並且是有序的
  2. std::mulitset可以有重復的元素並且是有序的
  3. std::unordered_set具有唯一元素且未排序
  4. std::unordered_mulitset可以有重復的元素並且是無序的

因此,我們可以根據我們的願望或要求選擇任何類型的套裝。

由於我們使用 C++ 並進行面向對象編程,因此我們會將所有內容包裝在一個類(結構)中並覆蓋 2 個運算符:

  1. operator *用於創建笛卡爾積
  2. operator <<用於簡單輸出

所以,如果我們改變班級內部的某些東西,外部世界就不會受到影響。 有了它,我們還可以使用std::vector來存儲元素。 沒什么大不了的。

並且,為了實現,我們將添加一個用於構建笛卡爾積的特殊功能。 通常情況下,2 組的乘積,至少有一組是空的,將導致一個空集。 但是,因為我們想為一整套集合做笛卡爾積,我們想從一個空集合開始,然后在一個循環中建立所有后續的集合。

因為在我們的例子中,2 個元素的笛卡爾積是一個串聯,所以基本上是一個加法,我們可以接受一個空的集合(和一個中性元素)。 親愛的所有數學家:請憐憫並原諒我這樣做!

通過所有這些准備工作,我們將構建一個非常簡單的包裝類和一個更簡單、更直觀的“main”函數。 請參閱以下許多可能的解決方案之一:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <set>
#include <unordered_set>

using Element = std::string;
using SetImplementation = std::unordered_set<Element>;

// Small wrapper to add additional functionality to our set
struct CSet {

    SetImplementation elements{};

    // Calculate the cartesian product
    CSet operator * (const CSet& B) {

        // Special handling. If one of the sets is empty, we return the other
        if (elements.empty()) return B; if (B.elements.empty()) return *this;

        // The resulting set containing the cartesian product
        CSet cartesianProduct{};
       
        // For all elements in set A and set B
        for (const Element a : elements) for (const Element b : B.elements)

            // For strings, the cartesian product is concatenation
            cartesianProduct.elements.insert( a + b );
        return cartesianProduct;
    }
    // Create the desired output for the set
    friend std::ostream& operator << (std::ostream& os, const CSet& s) {
        if (not s.elements.empty()) {
            os << "Len: " << s.elements.begin()->size() << '\n';
            std::copy(s.elements.begin(), s.elements.end(), std::ostream_iterator<Element>(os, "\n"));
        }
        return os;
    }
};

// Test data
const std::vector<CSet> testSets{ 
    {{"l", "m", "n"}},
    {{"v", "g", "h", "k", "L", "Z", "a", "b", "d"}},
    {{"M", "q", "r", "u", "v", "g", "h", "k", "l"}},
    {{"M", "Q", "R", "Z", "a", "b", "d"}},
    {{"M", "Q", "R", "d"}},
    {{"d"}} };

// Driver Code
int main() {

    CSet cartesianProduct{};
    CSet& A{ cartesianProduct };

    for (const CSet& B : testSets) {
        cartesianProduct = A * B;
        std::cout << cartesianProduct;
    }
}

正如所說。 您可以使用各種不同的集合。 只需更改頂部的 using staement,它就會繼續工作。


如果您更喜歡使用std::vector ,那么這也是可能的。 我們只會修改類內部。 外部代碼保持原樣。 請看下面的一段代碼:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>

using Element = std::string;
using SetImpl = std::vector<Element>;

struct Set {
    std::vector<Element> elements{};

    Set operator * (const Set& B) {

        if (elements.empty()) return B;
        if (B.elements.empty()) return *this;

        Set cartesianProduct{}; 
        cartesianProduct.elements.resize(elements.size() * B.elements.size());
        auto product{ cartesianProduct.elements.begin() };

        for (const Element a : elements) for (const Element b : B.elements)
            *product++ = a + b;
        return cartesianProduct;
    }

    friend std::ostream& operator << (std::ostream& os, const Set& s) {
        if (not s.elements.empty()) {
            os << "Len: " << s.elements[0].size() << '\n';
            std::copy(s.elements.begin(), s.elements.end(), std::ostream_iterator<Element>(os,"\n"));
        }
        return os;
    }
};

std::vector<Set> testSets{ {{"a","b"}}, {{"c"}}, {{"e", "f", "g"}} };

int main() {

    Set cartesianProduct{}, &A{ cartesianProduct };

    for (const Set& B : testSets) {
        cartesianProduct = A * B;
        std::cout << cartesianProduct;
    }
}

#endif



#if 0
#include <iostream>
#include <cctype>
#include <climits>
#include <string>
#include <algorithm>
#include <iterator>

int main() {

    // Loop. unitl the input number is valid
    for (bool numberIsValid{}; not numberIsValid;) {

        // Instruct user on what to do
        std::cout << "\n\nEnter a number (digits only, no leading 0es) that you would like to reverse:\n";

        // Read a number as string and check, if it is valid
        if (std::string numberAsString{}; std::getline(std::cin, numberAsString) and
            not numberAsString.empty() and
            std::all_of(numberAsString.begin(), numberAsString.end(), std::isdigit) and
            not (numberAsString[0] == '0') and
            numberAsString.size() < std::numeric_limits<unsigned int>::digits10)
        {
            // Number is valid
            numberIsValid = true;
            std::cout << "\n\nReverse number is:\t ";
            std::copy(numberAsString.rbegin(), numberAsString.rend(), std::ostream_iterator<char>(std::cout));
        }
        else {
            std::cerr << "\nError: Invalid input\n\n";
        }
    }
}

暫無
暫無

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

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