0

Given 2 lists of data (list A & list B) I need an algorithm to generate all possible combination C such that in each combination:

  • All items of A must be present, ordering doesn't matter (e.g., abc is same as cba)
  • Exactly 1 item from B must be present after each item from A
  • Items from B can be repeated

Example 1

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

Answer:

a1b1c1
a1b1c2
a1b2c1
a1b2c2
a2b1c1
a2b1c2
a2b2c1
a2b2c2

Example 2

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

Answer:

a1b1
a1b2
a1b3
a2b1
a2b2
a2b3
a3b1
a3b2
a3b3

What algorithm can I follow to generate this answer? Thanks. I see the pattern, but unable to translate it to code. I'll be coding in C++, but an algorithm will work for me.

I've checked the following questions on SO about this topic. But none is like my problem.

How to find all combinations of sets of pairs - doesn't allow repetition on 2nd set.

All combinations of pairs within one set - deals with one set.

combinations between two lists? - doesn't allow repetition on 2nd set.

Finding all possible value combinations between two arrays - doesn't allow repetition on 2nd set.

An efficient method to generate all possible ways to pair up items in a data set - deals with single set, doesn't allow repetition.

I couldn't come up with a minimum example, as I don't know the algorithm.

Joseph Wood
  • 7,077
  • 2
  • 30
  • 65
Donotalo
  • 12,748
  • 25
  • 83
  • 121
  • 2
    Being a member for so long with that many questions, you really show know better. Time to re-read [the help pages](http://stackoverflow.com/help), especially ["What topics can I ask about here?"](http://stackoverflow.com/help/on-topic) and ["What types of questions should I avoid asking?"](http://stackoverflow.com/help/dont-ask). Also [re-take the SO tour](http://stackoverflow.com/tour) and [re.read about how to ask good questions](http://stackoverflow.com/help/how-to-ask). And of course re-learn how to create a [Minimal, Complete, and Verifiable Example](http://stackoverflow.com/help/mcve). – Some programmer dude Jul 31 '18 at 11:38
  • 1
    Also please read [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/), and all of http://idownvotedbecau.se/ to learn some reasons your question might be down-voted. Finally, please [learn how to debug your programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/). – Some programmer dude Jul 31 '18 at 11:38
  • 2
    @Someprogrammerdude: OK. Edited my question. You thought this is yet another pairing question? Probably that's not true and before posting here I searched for answer but couldn't find one. I know how to debug, but I couldn't code as I don't know the algorithm! Thank you, idownvotedbecau.se is interesting. – Donotalo Jul 31 '18 at 11:54
  • @JHBonarius: Sorry I'm missing - where you're seeing the application of permutation in the problem? – Donotalo Jul 31 '18 at 12:09
  • @Donotalo excuse my poop comment. I read "an algorithm to generate all possible combinations" and thought "permutation problem" I judged too soon. Still, this can't be so hard, not? – JHBonarius Jul 31 '18 at 12:12
  • 1
    @JHBonarius, it wasn't a poop comment. If you look solely at the elements from `b`, those are permutations with repetition of length `a.size()` – Joseph Wood Jul 31 '18 at 12:18
  • From the suggested result, it seems you must always alternate one element from `A` and one element from `B` for each output, is this correct? Moreover, it seems you don't reorder the element from `A`, the outcome is always `aXbYcZ` in your example. – Rerito Jul 31 '18 at 13:22
  • According to your bullet list of requirements these should also be solutions to Example 1: cba1, abc2, 1abc, a2bc. Voting to close as unclear. – acraig5075 Jul 31 '18 at 13:53
  • @acraig5075: You're right. I've fixed the description. This is a part of another problem and I had difficulty figuring out a description of what I was looking for. All I knew is the input and output. – Donotalo Jul 31 '18 at 16:35
  • This is a Cartesian product of B with itself, as many times as there are elements in A, with the results then decorated by using the constant A values. – Karl Knechtel Mar 02 '23 at 01:40

4 Answers4

1

In my solution, I make a counter – a vector with size of first list which stores iterators in second list. Then, everything becomes very easy (and even does not need much code for implementation.)

My sample code:

#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;
}

Output:

a1b1c1
a1b1c2
a1b2c1
a1b2c2
a2b1c1
a2b1c2
a2b2c1
a2b2c2

Live Demo on coliru

It's simply working like a trip meter where the first list provides number and labels of columns, and second list provides the values of columns:

Trip Meter in Wikipedia

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
1

These are permutations with repetition of the elements from B in disguise. If you are looking only at the elements from B (example from the OP) we have:

111
112
121
122
211
212
221
222

Which are precisely the permutations of B with repetition. The elements from A are simply filled in to obtain the desired result.

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;
            }
        }
    }
}

Calling it we have:

#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;
}

And here is the output:

Example 1 : 
a1b1c1
a1b1c2
a1b2c1
a1b2c2
a2b1c1
a2b1c2
a2b2c1
a2b2c2

Example 2 : 
a1b1
a1b2
a1b3
a2b1
a2b2
a2b3
a3b1
a3b2
a3b3

Here is a working example : https://ideone.com/OUv49P

Joseph Wood
  • 7,077
  • 2
  • 30
  • 65
  • 1
    this could very intrestingly be converted to work for combinations of N lists. – Samer Tufail Jul 31 '18 at 14:37
  • Another way to say "permutations with repetition" (more standardly, permutations with *replacement*) is that it is a Cartesian product of B with itself. – Karl Knechtel Mar 02 '23 at 01:41
  • @KarlKnechtel, First off, I've seen many ways to describe he fact that elements are non-distinct: e.g. "permutation with {repetition|replacement|repeats allowed|non-distinct}". I wouldn't say _replacement_ is the standard way. Many of the articles I run into daily use _without repetition_ or _with repetition_. See for example the wikipedia articles for [combinations with repetition](https://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition) and [permutations without repetitions](https://en.wikipedia.org/wiki/Permutation#Permutations_without_repetitions) – Joseph Wood Mar 02 '23 at 11:24
  • @KarlKnechtel, Secondly, you are right "permutations with repetition" are equivalent to the Cartesian product of B with itself. However, one could argue that the Cartesian product and permutations are fundamentally different as generating the product requires multiple objects where as generating permutations require a single object. I wrote more about this topic here [Non-redundant version of expand.grid](https://stackoverflow.com/a/68050873/4408538). FYI, `expand.grid` generates the Cartesian product. – Joseph Wood Mar 02 '23 at 11:34
1

Many possible solutions for this. Mine's building a string with a recursive call:

#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

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
1

Many solutions possible indeed, here is a my simple recursive solution, pretty similar to JHBonarious's but without the niceness of production code.

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;
}
Samer Tufail
  • 1,835
  • 15
  • 25
  • Interesting. What do you mean with "production code"? – JHBonarius Aug 01 '18 at 04:57
  • 1
    @JHBonarius the constness, the typedefs, the pretty variable names, the iterators, the aliases - not that its bad, don't get be wrong. I find people get tangled and forget about the core question, which is the algorithm. Could even be pseudo code. :) – Samer Tufail Aug 01 '18 at 05:12
  • Hmm, I partially agree. The code should in that case at least be self-explinatory. Yours is leaning on mine a bit now. IMHO We don't want to give askers the answers, we want them to understand them. – JHBonarius Aug 01 '18 at 08:54
  • Absolutely, just answers are no good, and I tend to provide long explanations to most of my answers. I wouldn't even have posted this but since I had already written it, I added it. If you think there less value/contribution, I can remove it. – Samer Tufail Aug 01 '18 at 09:22
  • 1
    No, feel free to keep it. It can always be useful for others, and that's what SO is about. – JHBonarius Aug 01 '18 at 09:27