【问题标题】:Getting Permutations with Repetitions in this special way以这种特殊的方式获得重复排列
【发布时间】:2020-11-10 03:22:57
【问题描述】:

我有一个 {a,b} 列表,我需要所有可能的组合,比如 n=3。

所以: [a,b,a], [b,a,b] [一个,一个,一个] [b,b,b]

等等

这样的问题有名字吗

我目前的解决方案只是使用随机抽样,效率非常低:

    void set_generator(const vector<int>& vec, int n){
        map<string, vector<int>> imap;
        int rcount = 0;
        while(1){
            string ms = "";
            vector<int> mset;
            for(int i=0; i<n; i++){
                int sampled_int = vec[rand() % vec.size()];
                ms += std::to_string(sampled_int);
                mset.emplace_back(sampled_int);
            }
            
            if(rcount > 100)
                break;
            if(imap.count(ms)){
                rcount += 1;
                //cout << "*" << endl;
                continue;
            }
            rcount = 0;
            imap[ms] = mset;
            cout << ms << endl;
            

        }
        
    }
    

        
    set_generator({1,2},3); 

【问题讨论】:

标签: c++ combinations


【解决方案1】:

让我们称 b 为输入向量的大小。

问题在于生成从 0 到 b^n - 1 的所有数字,以 b 为底。

一个简单的解决方案是逐个递增数组的元素,每个元素从 0 到 b-1。

这是由后面代码中的函数increment 执行的。

输出:

111
211
121
221
112
212
122
222

代码:

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

void set_generator_op (const std::vector<int>& vec, int n){
        std::map<std::string, std::vector<int>> imap;
        int rcount = 0;
        while(1){
            std::string ms = "";
            std::vector<int> mset;
            for(int i=0; i<n; i++){
                int sampled_int = vec[rand() % vec.size()];
                ms += std::to_string(sampled_int);
                mset.emplace_back(sampled_int);
            }
            
            if(rcount > 100)
                break;
            if(imap.count(ms)){
                rcount += 1;
                //cout << "*" << endl;
                continue;
            }
            rcount = 0;
            imap[ms] = mset;
            std::cout << ms << "\n";
        }
    }
    
// incrementation of a array of int, in base "base"
// return false if max is already attained
bool increment (std::vector<int>& cpt, int base) {
    int n = cpt.size();
    for (int i = 0; i < n; ++i) {
        cpt[i]++;
        if (cpt[i] != base) {
            return true;
        }
        cpt[i] = 0;
    }
    return false;
}

void set_generator_new (const std::vector<int>& vec, int n){
    int base = vec.size();
    std::vector<int> cpt (n, 0);
    
    while (true) {
        std::string permut = "";
        for (auto &k: cpt) {
            permut += std::to_string (vec[k]);
        }
        std::cout << permut << "\n";
        if (!increment(cpt, base)) return;
    }
}
    
int main() {   
    set_generator_op ({1,2},3); 
    std::cout << "\n";
    set_generator_new ({1,2},3); 
}

按照 Jarod42 的建议,我有

  • 抑制了无用的字符串转换
  • 使用更优雅的do ... while 而不是while true
  • 反转迭代器以打印结果

此外,我已经创建了程序的模板版本。

新输出:

111
112
121
122
211
212
221
222

aaa
aab
aba
abb
baa
bab
bba
bbb

还有新代码:

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


// incrementation of a array of int, in base "base"
// return false if max is already attained
bool increment (std::vector<int>& cpt, int base) {
    int n = cpt.size();
    for (int i = 0; i < n; ++i) {
        cpt[i]++;
        if (cpt[i] != base) {
            return true;
        }
        cpt[i] = 0;
    }
    return false;
}

template <typename T>
void set_generator_new (const std::vector<T>& vec, int n){
    int base = vec.size();
    std::vector<int> cpt (n, 0);
    do {
        for (auto it = cpt.rbegin(); it != cpt.rend(); ++it) {
            std::cout << vec[*it];
        }
        std::cout << "\n";
    } while (increment(cpt, base));
}
    
int main() {   
    set_generator_new<int> ({1,2}, 3); 
    std::cout << "\n";
    set_generator_new<char> ({'a','b'}, 3); 
}

【讨论】:

  • 对我来说似乎并没有低效。您对优化有什么想法?
  • @PatrickParker 经过进一步思考,我意识到increment 中的平均迭代次数以b/(b-1) 为界,对应于无穷大的n。最坏的情况是b=2,我们通过生成一个新向量得到平均 2 次迭代。如此有效,并非真的低效!我删除了效率低下的提及。
  • 我认为问题要求组合,所以 211、121、112 都是相同的,只应打印或生成一个。我可能是错的,只是一个想法。
  • @swag2198 OP 的代码生成 8 种可能的组合。所以我想这就是他们想要的。文本有点模棱两可。
  • 我的意思是that
【解决方案2】:

除了整数使用的具体答案之外,我还想提供一种在测试用例构建过程中需要的通用方法,用于各种参数变化广泛的场景。也许它对你也有帮助,至少对于类似的场景。

#include <vector>
#include <memory>

class SingleParameterToVaryBase
{
public:

  virtual bool varyNext() = 0;
  virtual void reset() = 0;
};

template <typename _DataType, typename _ParamVariationContType>
class SingleParameterToVary : public SingleParameterToVaryBase
{
public:

  SingleParameterToVary(
    _DataType& param,
    const _ParamVariationContType& valuesToVary) :
      mParameter(param)
    , mVariations(valuesToVary)
  {
    if (mVariations.empty())
      throw std::logic_error("Empty variation container for parameter");
    reset();
  }

  // Step to next parameter value, return false if end of value vector is reached
  virtual bool varyNext() override
  {
    ++mCurrentIt;

    const bool finished = mCurrentIt == mVariations.cend();

    if (finished)
    {
      return false;
    }
    else
    {
      mParameter = *mCurrentIt;
      return true;
    }
  }

  virtual void reset() override
  {
    mCurrentIt = mVariations.cbegin();
    mParameter = *mCurrentIt;
  }

private:

  typedef typename _ParamVariationContType::const_iterator ConstIteratorType;

  // Iterator to the actual values this parameter can yield
  ConstIteratorType mCurrentIt;

  _ParamVariationContType mVariations;

  // Reference to the parameter itself
  _DataType& mParameter;
};

class GenericParameterVariator
{
public:

  GenericParameterVariator() : mFinished(false)
  {
    reset();
  }

  template <typename _ParameterType, typename _ParameterVariationsType>
  void registerParameterToVary(
    _ParameterType& param,
    const _ParameterVariationsType& paramVariations)
  {
    mParametersToVary.push_back(
      std::make_unique<SingleParameterToVary<_ParameterType, _ParameterVariationsType>>(
        param, paramVariations));
  }

  const bool isFinished() const { return mFinished; }

  void reset()
  {
    mFinished = false;
    mNumTotalCombinationsVisited = 0;

    for (const auto& upParameter : mParametersToVary)
      upParameter->reset();
  }

  // Step into next state if possible
  bool createNextParameterPermutation()
  {
    if (mFinished || mParametersToVary.empty())
      return false;

    auto itPToVary = mParametersToVary.begin();
    while (itPToVary != mParametersToVary.end())
    {
      const auto& upParameter = *itPToVary;

      // If we are the very first configuration at all, do not vary.
      const bool variedSomething = mNumTotalCombinationsVisited == 0 ? true : upParameter->varyNext();
      ++mNumTotalCombinationsVisited;

      if (!variedSomething)
      {
        // If we were not able to vary the last parameter in our list, we are finished.
        if (std::next(itPToVary) == mParametersToVary.end())
        {
          mFinished = true;
          return false;
        }

        ++itPToVary;
        continue;
      }
      else
      {
        if (itPToVary != mParametersToVary.begin())
        {
          // Reset all parameters before this one
          auto itBackwd = itPToVary;
          do
          {
            --itBackwd;
            (*itBackwd)->reset();
          } while (itBackwd != mParametersToVary.begin());
        }

        return true;
      }
    }

    return true;
  }

private:

  // Linearized parameter set
  std::vector<std::unique_ptr<SingleParameterToVaryBase>> mParametersToVary;

  bool mFinished;
  size_t mNumTotalCombinationsVisited;

};

可能的用法:

GenericParameterVariator paramVariator;

  size_t param1;
  int param2;
  char param3;

  paramVariator.registerParameterToVary(param1, std::vector<size_t>{ 1, 2 });
  paramVariator.registerParameterToVary(param2, std::vector<int>{ -1, -2 });
  paramVariator.registerParameterToVary(param3, std::vector<char>{ 'a', 'b' });

  std::vector<std::tuple<size_t, int, char>> visitedCombinations;
  while (paramVariator.createNextParameterPermutation())
    visitedCombinations.push_back(std::make_tuple(param1, param2, param3));

生成:

(1, -1, 'a')
(2, -1, 'a')
(1, -2, 'a')
(2, -2, 'a')
(1, -1, 'b')
(2, -1, 'b')
(1, -2, 'b')
(2, -2, 'b')

当然,这可以进一步优化/专业化。例如,如果你想避免有效的重复,你可以简单地添加一个散列方案和/或一个避免函子。此外,由于参数作为引用保存,因此可以考虑通过删除复制/赋值构造函数和运算符来保护生成器免受可能的错误使用。

时间复杂度在理论置换复杂度范围内。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-13
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    • 2022-06-28
    • 1970-01-01
    • 2014-11-20
    • 1970-01-01
    相关资源
    最近更新 更多