【问题标题】:Generating all combinations from two lists with repetition从两个重复的列表中生成所有组合
【发布时间】:2018-07-31 11:35:28
【问题描述】:

给定 2 个数据列表(列表 A 和列表 B),我需要一个算法来生成所有可能的组合 C,以便在每个组合中:

  • A 的所有项目都必须存在,排序无关紧要(例如,abccba 相同)
  • B 中的恰好 1 项必须出现在 A 中的每个项目之后
  • B 中的项目可以重复

示例 1

A = {a, b, c}
B = {1, 2}

答案:

a1b1c1
a1b1c2
a1b2c1
a1b2c2
a2b1c1
a2b1c2
a2b2c1
a2b2c2

示例 2

A = {a, b}
B = {1, 2, 3}

答案:

a1b1
a1b2
a1b3
a2b1
a2b2
a2b3
a3b1
a3b2
a3b3

我可以遵循什么算法来生成这个答案?谢谢。我看到了模式,但无法将其转换为代码。我将使用 C++ 进行编码,但算法会为我工作。

我已经检查了有关此主题的以下关于 SO 的问题。但没有一个像我的问题。

How to find all combinations of sets of pairs - 不允许在第二组重复。

All combinations of pairs within one set - 处理一组。

combinations between two lists? - 不允许在第二组重复。

Finding all possible value combinations between two arrays - 不允许在第二组重复。

An efficient method to generate all possible ways to pair up items in a data set - 处理单组,不允许重复。

因为我不知道算法,所以我想不出一个最小的例子。

【问题讨论】:

标签: c++ algorithm combinations permutation


【解决方案1】:

在我的解决方案中,我制作了一个计数器——一个大小为第一个列表的向量,它将迭代器存储在第二个列表中。然后,一切都变得非常简单(甚至不需要太多代码来实现。)

我的示例代码:

#include <iostream>
#include <list>
#include <vector>

typedef std::list<char> List1;
typedef std::list<int> List2;
typedef std::vector<List2::const_iterator> Counter;

std::ostream& operator << (std::ostream &out, const std::pair<List1&, Counter&> &pair)
{
  Counter::const_iterator iter = pair.second.begin();
  for (const List1::value_type value : pair.first) {
    out << value << **iter++;
  }
  return out;
}

bool count(const List2 &lst2, Counter &counter)
{
  for (size_t i = counter.size(); i--;) {
    if (++counter[i] != lst2.end()) return false;
    counter[i] = lst2.begin();
  }
  return true; // wrap-over
}

int main()
{
  List1 lst1 = { 'a', 'b', 'c' };
  List2 lst2 = { 1, 2 };
  // make/fill counter
  std::vector<List2::const_iterator> counter(lst1.size(), lst2.begin());
  do {
    std::cout << std::pair<List1&, Counter&>(lst1, counter) << '\n';
  } while (!count(lst2, counter));
  // done
  return 0;
}

输出:

a1b1c1
a1b1c2
a1b2c1
a1b2c2
a2b1c1
a2b1c2
a2b2c1
a2b2c2

Live Demo on coliru

它就像trip meter 一样工作,第一个列表提供列的数量和标签,第二个列表提供列的值:

【讨论】:

    【解决方案2】:

    许多可能的解决方案。我正在使用递归调用构建一个字符串:

    #include <vector>
    #include <iostream>
    #include <string>
    
    using charVec = std::vector<char>;
    using intVec = std::vector<int>;
    
    void printEl(const std::string& start, charVec::const_iterator it, const charVec::const_iterator& endIt, const intVec& iV)
    {
        if (it == endIt)
        {
            std::cout << start << "\n";
            return;
        }
        for (const auto& iVel : iV)
        {
            printEl(start + *it + std::to_string(iVel), it + 1, endIt, iV);
        }
    }
    
    void printComb(const charVec& cV, const intVec& iV)
    {
        printEl("", cV.cbegin(), cV.cend(), iV);
    }
    
    int main()
    {
        charVec A1{ 'a', 'b', 'c' };
        intVec B1{ 1, 2 };
    
        printComb(A1, B1);
    
        std::cout << "next example: \n";
    
        charVec A2{ 'a', 'b' };
        intVec B2{ 1, 2, 3 };
    
        printComb(A2, B2);
    }
    

    live example

    【讨论】:

      【解决方案3】:

      这些是变相重复来自B 的元素的排列。如果您只查看来自 B 的元素(来自 OP 的示例),我们有:

      111
      112
      121
      122
      211
      212
      221
      222
      

      这正是B 的重复排列。 A 中的元素只需填写即可获得所需的结果。

      template <typename typeVec1, typename typeVec2>
      void customPerms(typeVec1 a, typeVec2 b) {
      
          int r = a.size(), n = b.size();
          int r1 = r - 1, n1 = n - 1;
          std::vector<int> z(r, 0);
          int numRows = (int) std::pow(n, r);
      
          for (int i = 0; i < numRows; ++i) {
              for (int j = 0; j < r; ++j)
                  std::cout << a[j] << b[z[j]];
              std::cout << std::endl;
      
              for (int k = r1; k >= 0; --k) {
                  if (z[k] != n1) {
                      ++z[k];
                      break;
                  } else {
                      z[k] = 0;
                  }
              }
          }
      }
      

      我们有这样的称呼:

      #include <iostream>
      #include <cmath>
      #include <vector>
      
      int main() {
          std::cout << "Example 1 : " << std::endl;
          std::vector<std::string> a1 = {"a", "b", "c"};
          std::vector<int> b1 = {1, 2};
          customPerms(a1, b1);
      
          std::cout << "\nExample 2 : " << std::endl;
          std::vector<char> a2 = {'a', 'b'};
          std::vector<int> b2 = {1, 2, 3};
          customPerms(a2, b2);
          return 0;
      }
      

      这是输出:

      Example 1 : 
      a1b1c1
      a1b1c2
      a1b2c1
      a1b2c2
      a2b1c1
      a2b1c2
      a2b2c1
      a2b2c2
      
      Example 2 : 
      a1b1
      a1b2
      a1b3
      a2b1
      a2b2
      a2b3
      a3b1
      a3b2
      a3b3
      

      这是一个工作示例:https://ideone.com/OUv49P

      【讨论】:

      • 这可以非常有趣地转换为适用于 N 个列表的组合。
      【解决方案4】:

      确实有很多解决方案,这是我的一个简单的递归解决方案,与 JHBonarious 的非常相似,但没有生产代码的niceness

      void permute_helper(vector<string>& A, vector<string>& B, int ii, string curr)
      {
          if(ii == A.size())
          {
              cout << curr << endl;
              return;
          }
      
          for(int i = 0; i < B.size(); ++i) permute_helper(A, B, ii + 1, curr + A[ii] + B[i]);
      }
      
      void permute(vector<string>& A, vector<string>& B)
      {
          permute_helper(A,B,0,"");
      }
      
      int main() {
          vector<string> A = {"a","b","c"};
          vector<string> B = {"1","2"};
          permute(A,B);
          return 0;
      }
      

      【讨论】:

      • 有趣。 “生产代码”是什么意思?
      • @JHBonarius 常量、类型定义、漂亮的变量名、迭代器、别名——并不是说它不好,不要误会。我发现人们很纠结,忘记了核心问题,也就是算法。甚至可能是伪代码。 :)
      • 嗯,我部分同意。在这种情况下,代码至少应该是不言自明的。你的现在有点靠我了。恕我直言,我们不想给提问者答案,我们希望他们能够理解。
      • 当然,只有答案是不好的,而且我倾向于对我的大部分答案提供很长的解释。我什至不会发布这个,但因为我已经写了它,所以我添加了它。如果您认为价值/贡献较少,我可以将其删除。
      • 不,请随意保留。它总是对其他人有用,这就是 SO 的意义所在。
      猜你喜欢
      • 1970-01-01
      • 2013-06-16
      • 1970-01-01
      • 2015-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多