【问题标题】:Get all combinations of elements of all lengths of 2D array in C++在C ++中获取所有长度的二维数组元素的所有组合
【发布时间】:2021-10-17 15:41:44
【问题描述】:

我有一些行和列的二维字符数组,例如unsigned char arr[rows][columns]

例如,假设 rows = 10,columns = 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 行的所有字符组合。等等。这可以用来减少重复。也许,它甚至允许固定数量的嵌套循环,而不需要那种里程计方法。
  • 这是一个常见的组合学东西,它可能有一个名字,但我不知道。无论如何,生成所有这些组合就像在一个不同于 10 的基数中计数。为了方便说有 10 列和 3 次投掷。然后,如果我们让行中的项目每 3 位或更少的基数为 10 的数字具有从零开始的索引,则表示其中一种组合,例如123 表示从第一行取第 1 项,从第二行取第 2 项,从第三行取第 3 项。现在它很复杂,因为行可以有不同数量的项目,但你可以处理它。

标签: c++ multidimensional-array


【解决方案1】:

我试图实现我的另一个想法:

另一个观察:对于 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

Live Demo on coliru

【讨论】:

    【解决方案2】:

    这是解决方案的递归版本——基于以下事实的递归:长度 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";
        }
    }
    

    【讨论】:

    • 递归的想法其实很好。 (太糟糕了,我没有它。);-) 然而,你的 MCVE 并没有产生预期的输出。 (看看coliru。)还有一些弱点: 1. void main() - 按照标​​准,main() 必须有一个整数返回类型。 2. for (const auto&amp; combo : all_combinations(rows, 5)) 不用硬编码值 (5),它可以很容易地从 rows.size() 计算出来(更便于维护)。 3.std::vector&lt;std::vector&lt;std::string&gt;&gt;存储chars?
    • 但是,OP 似乎没有反应。 (我把它当作一个很好的谜语来度过我的周日晚上,并且很高兴让它运行起来,但除此之外,我不确定它是否值得付出努力。)
    【解决方案3】:

    通常情况下,一个好的解决方案是

    • 首先分析需求并
    • 然后了解数学背景,
    • 做适当的设计,
    • 使用适当的 C++ 容器和元素
    • 最后,编写代码

    分析需求后发现,数学背景是构建2个集合的笛卡尔积。

    集合中的元素是std::strings。我们从只有一个字符的字符串开始,然后构建笛卡尔积。结果是一个包含 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 &lt;&lt; 用于简单输出

    所以,如果我们在班级内改变某些东西,外部世界不会受到影响。有了它,我们还可以使用std::vector 来存储元素。没什么大不了的。

    而且,为了实现,我们将添加一个特殊功能来构建笛卡尔积。通常 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;
        }
    }
    

    如前所述。你可以使用各种不同的套装。只需更改顶部的使用状态,它将继续工作。


    如果您更喜欢使用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";
            }
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 2022-01-25
      • 1970-01-01
      • 2011-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-16
      • 1970-01-01
      相关资源
      最近更新 更多