-1

We have a classroom. Our main goal is to make pairs of students to work together. How are we going to do it? Through a matrix. This matrix (n x n, n is pair) stores the level of 'preference' each student has with another one. For example, i is a student and j is another student:

matrix[i][j] = 40

matrix[j][i] = 20

So, the preference level of (i,j) might be different that the pair (j,i).

Let's assume we have 10 students. The first iteration will make this pairs: (0,1), (2,3), (4,5) ... (9,10). The next one will be: (0,2), (1,3), (4,5) ... (9,10) - and so on.

So, we need to find a solution using a backtracking algorithm that gets the highest value for this purpose. And the solution is a vector with the pairs that make this maximum value.

We think the right way would be to generate a tree, but we don't know how to generate it.

The last thing we've tried was through a method that calculates how many iterations the program will need to make all pairs, and using modules, know when we need to change the order in the solution vector. I'm not a huge fan of this approach. Anyways, we can't make it work.

As the matrix is generated randomly, we have not an actual 'expected result'. What we need is a way that ensures every pair and possible combination of students is done.

genpfault
  • 51,148
  • 11
  • 85
  • 139
alexhzr
  • 153
  • 1
  • 1
  • 12
  • what is your question? – 463035818_is_not_an_ai May 09 '19 at 14:09
  • How to get all possible combinations of students (0,1), (2,3), (4,5) ... // (0,2), (1, 3), (4,5) ... // (0,3), (1,2), (4,5) ... // – alexhzr May 09 '19 at 14:13
  • do you want to minimize general dissatisfaction with their pairs ? – eparvan May 09 '19 at 14:15
  • We want to maximize the general satisfaction with their pairs. This being said, find the best way to sort this students and ensure we obtain the highest global satisfaction level. – alexhzr May 09 '19 at 14:19
  • But how are these two nested loops? Everytime you change the initial pair, there should be a new tree including all possibilities... – alexhzr May 09 '19 at 14:30
  • Why not using an already existing matching algorithm like this one: http://www.nrmp.org/matching-algorithm/ – Toady May 09 '19 at 14:32
  • How do you define `global satisfaction` ? – fjardon May 09 '19 at 15:32
  • You are looking for a maximum value. Suppose you have the pairs (0,1), (2,3), (4,5) ... (9,10). How would you calculate the "value" for these pairs? Is the value going to be the same as for the pairs (1,0), (3,2), (5,4) ... (10,9) even though the stored preferences are different? (I.e. are you looking at ordered pairs or unordered subsets?) – JaMiT May 09 '19 at 16:37
  • Are we to assume `n` is even? – JaMiT May 09 '19 at 16:39
  • "But how are these two nested loops?" sorry I completely misunderstood the question ;) – 463035818_is_not_an_ai May 10 '19 at 22:22
  • I'm wondering if there might be a solution that doesn't actually need to evaluate at all the possible pair-sets, based on a singular value decomposition or something similar. Haven't come up with anything yet; it might be worth asking on https://math.stackoverflow.com/ – aschepler May 11 '19 at 14:02

3 Answers3

1

So you have a square matrix each i-th row in which represents all possible pairs for student i and each student can have only one pair.

To get all possible combinations of pairs you may use the following recursion:

  1. iterate throw all possible pairs for i-th student:

    • if pair is possible(not himself, and pair not in use), then mark i-th student and its pair as 'used' and store it.
    • if (i+1) is less then students count, then go to step 1 with (i+1)-th student, otherwise return stored pairs.

You may encounter some difficulties if the number of students is odd number. In this case you may add a "fake" student with maximum tolerance to any pair. So you always be able to make pairs and calculate overall satisfaction.

Here is a java snippet of one of algorithms that finds all possible variations of pairs:

    List<List<Integer[]>> allVariationsOfPairs = new ArrayList<>();

    void retrieveAllVariationsOfPairs(int[][] array, int studentIndex, List<Integer[]> pairs) {
        if (studentIndex == array.length) {
            allVariationsOfPairs.add(pairs);
            return;
        }
        boolean hasPair = false;
        for (int i = 0; i < array[studentIndex].length; ++i) {
            if (studentIndex == i || array[studentIndex][i] == 1 || array[studentIndex][studentIndex] == 1) {
                continue;
            }
            hasPair = true;
            List<Integer[]> copyPairs = new ArrayList<>(pairs);
            copyPairs.add(new Integer[]{studentIndex, i});
            int[][] copyArray = Arrays.stream(array).map(r -> r.clone()).toArray(int[][]::new);
            for (int[] row : copyArray) {
                row[studentIndex] = 1;
                row[i] = 1;
            }
            retrieveAllVariationsOfPairs(copyArray, studentIndex + 1, copyPairs);
        }
        if (!hasPair) {
            retrieveAllVariationsOfPairs(array, studentIndex + 1, pairs);
        }
    }

Usage example:

retrieveAllVariationsOfPairs(new int[6][6], 0, new ArrayList<>());

Output:

[0, 1]
[2, 3]
[4, 5]

[0, 1]
[2, 4]
[3, 5]

[0, 1]
[2, 5]
[3, 4]

[0, 2]
[1, 3]
[4, 5]

[0, 2]
[1, 4]
[3, 5]

[0, 2]
[1, 5]
[3, 4]

[0, 3]
[1, 2]
[4, 5]

[0, 3]
[1, 4]
[2, 5]

[0, 3]
[1, 5]
[2, 4]

[0, 4]
[1, 2]
[3, 5]

[0, 4]
[1, 3]
[2, 5]

[0, 4]
[1, 5]
[2, 3]

[0, 5]
[1, 2]
[3, 4]

[0, 5]
[1, 3]
[2, 4]

[0, 5]
[1, 4]
[2, 3]

After you can calculate overall satisfaction for all set of pairs and choose most suitable set.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
eparvan
  • 1,639
  • 1
  • 15
  • 26
  • 3
    A Java snippet for a question tagged for a different language (C++)? Pseudocode would be more appropriate. – JaMiT May 09 '19 at 16:23
1

Your question reminds me about minimum transportation cost problem. It's a well known type of linear programming problems and your issue might be a special case of it.

Here is an example of possible cost table:

╔═══════════╦════════════╦═════════════╦═════════════╦═════════════╗
║           ║ Student A  ║ Student B   ║ Student C   ║  Supply     ║
╠═══════════╬════════════╬═════════════╬═════════════╬═════════════╣
║           ║DissatisfAB ║DissatisfBA  ║DissatisfCA  ║     1       ║
║           ║DissatisfAC ║DissatisfBC  ║DissatisfCB  ║     1       ║
║ Demand    ║    1       ║      1      ║     1       ║             ║
╚═══════════╩════════════╩═════════════╩═════════════╩═════════════╝

Each student demands one 'pair' and each student can supply itself to the others. As a cost of transportation we can use degree of dissatisfaction with their pair. Solution of this problem will fulfill the demand and minimize overall dissatisfaction.

Sure you can find a lot of libraries that solve this problem in c++. Or you can even try some online calculators

eparvan
  • 1,639
  • 1
  • 15
  • 26
1

I think this is an interesting problem and it requires some clever dynamic programming. However, I would start with some simple brute force and then try to refine it. As I understand your question, you are somewhat in that stage and try to find a way to enumerate all possible pairings.

Visualize it

For 4 students you have three possible combinations

(0 1) (2 3)       (0 2) (1 3)      (0 3) (1 2)

    1 2 3 
0   x o o            o x o             o o x
1     o o              o x               x o
2       x                o                 o

Note that we only need to draw half of the matrix, because it is symmetric (if 1 is paired with 2 then also 2 is paired with 1). We also can ignore the diagonal. Already with 4 students it looks somewhat complicated. So let's get this straight.

Counting

Lets say you have N students not yet assigned to a pair. How many combinations are there? Lets call it C(N)...

For 2 students there is only one combination, hence C(2)= 1.

For more than two unassigned students we can pick the first student without loss of generality. There are N-1 other student to whom we could pair him, hence in total C(N) = N-1 * C(N-2).

Lets make it a bit more concrete by listing the numbers:

N    N-1    C(N) 
2     1      1
4     3      3
6     5     15
8     7    105
...
n    n-1  (n-1)!!

Now we already know how to count them. There are 105 possibilities for 8 students. And in general for n students there are (n-1)!! possibilities ( x!! == x*(x-2)*(x-4)*...).

Constructing

Already while counting we used the following strategy to construct the solution:

  • Pick the first free student
  • Pick one of the other free students
  • Repeat with the rest

Obviously we need n/2 steps to have all students assigned to a pair. Lets consider an example. With 6 students, we have

( 5 ) * ( 3 ) * ( 1 )

possible combinations. Next we realize that we can always use an index to enumare only the students that are still available. Hence the indices we have to pick are

[0...4] x [0...2] x [0]

Now if for example you want to know what is the 5th combination you can get it in the following way...

Once we pick the first pair, there are still 3 possible choices for the second index (one only one to make the last pair from the only two available students). Hence you get the indices as

x0 = 5/3;       // -> 1
x1 = (5-x0)/1;  // -> 2

Ie that would be

  • We pick the first student for the first pair: 0
  • Available studnets at this point: available = {1,2,3,4,5}
  • We pick available[x0] to pair him with 0 : (0 2)
  • Available students at this point: available = {1,3,4,5}
  • We pick the first available for the next pair: 1
  • Available students at this point: available = {3,4,5}
  • We pick available[x1] to pair him with 1 : (1 5)
  • Only two left for the last pair (3 4)

-> The pairing with index 5 is (0 2)(1 5)(3 4).

Note that is might not be the most efficient way when implemented too literally, though it can be a starting point.

The code

For counting the combinations we need the x!! function (!! in the sense as explained above):

size_t double_fac(int n){
    size_t result = 1;
    while(n > 0) {
        result*=n;
        n-=2;
    }
    return result;
}

Using this I can compute the total number of combinations

size_t total_number_of_combinations(size_t n_students){ 
    return double_fac(n_students-1); 
}

I will need a function to find the index of the nth not yet assigned student, for this I will use some helper functions:

template <typename IT>
IT find_skip(IT begin,IT end,size_t skip,typename IT::value_type x){
    if (skip){
        return find_skip( ++ std::find(begin,end,x), end, skip-1,x);
    } else {
        return std::find(begin,end,x);
    }
}

template <typename IT>
size_t find_skip_index(IT begin,IT end,size_t skip,typename IT::value_type x){
    return std::distance(begin,find_skip(begin,end,skip,x));
}

Also I will use a flat index and then expand it as outline above (actually I dont like the above explanation too much, but I hope it is convincing enough...):

std::vector<size_t> expand_index(size_t n_students, size_t flat_index){
    std::vector<size_t> expanded_index;
    auto students_to_be_assigned = n_students;
    for (unsigned step=0;step<n_students/2;++step){
        int size_of_subspace = total_number_of_combinations(students_to_be_assigned-2);
        auto x = flat_index / size_of_subspace;
        expanded_index.push_back(x);
        flat_index -= x*size_of_subspace;
        students_to_be_assigned-=2;
    }
    return expanded_index;
}

In a nutshell: In each step I will choose a partner for the first free student. For flat_index == 0 the first pair is (0 1). Because there are size_of_subspace == total_number_of_combinations(n_students-2) combinations after picking that pair, the index of picking (0 2) as first pair is flat_index==size_of_subspace. However, please dont get confused, I do not transform the flat_index directly into the students index, but rather expandend_index == n refers to the nth not yet assigned student.

Putting it together:

using combination = std::vector<std::pair<size_t,size_t>>;

combination nth_combination(size_t n_students,size_t flat_index){
    combination result;
    auto expanded_index = expand_index(n_students,flat_index);      
    std::vector<bool> available(n_students,true);
    for (const auto& index : expanded_index) {
        std::pair<size_t,size_t> next_pair;
        next_pair.first = find_skip_index(available.begin(),available.end(),0,true);
        available[next_pair.first] = false;
        next_pair.second = find_skip_index(available.begin(),available.end(),index,true);
        available[next_pair.second] = false;
        result.push_back(next_pair);
    }
    return result;
}

Now for taking again n_students == 6 as example, this:

template <typename T>
void print_pairs(const T& t){
    for (auto e: t) std::cout << "(" << e.first << "," << e.second << ") ";    
    std::cout << "\n";
}

int main(){
    size_t n_students = 6;
    for (size_t i=0;i<total_number_of_combinations(n_students);++i){
        std::cout << i << "\t";
        print_pairs(nth_combination(n_students,i));
    }
}

prints:

0   (0,1) (2,3) (4,5) 
1   (0,1) (2,4) (3,5) 
2   (0,1) (2,5) (3,4) 
3   (0,2) (1,3) (4,5) 
4   (0,2) (1,4) (3,5) 
5   (0,2) (1,5) (3,4) 
6   (0,3) (1,2) (4,5) 
7   (0,3) (1,4) (2,5) 
8   (0,3) (1,5) (2,4) 
9   (0,4) (1,2) (3,5) 
10  (0,4) (1,3) (2,5) 
11  (0,4) (1,5) (2,3) 
12  (0,5) (1,2) (3,4) 
13  (0,5) (1,3) (2,4) 
14  (0,5) (1,4) (2,3) 

I hope with this output also the algorithm gets more clear. After picking the first pair, there are 3 possibilities for the second pair and only one combination for the last.

Live Demo

Disclaimer: As mentioned above, I am not claiming that this is an efficient implementation. The code is rather meant to be a verbose reference implementation. I am basically traversing the tree from the root till one of its leaves for each flat_index. In the next iteration one could think of traversing the tree only as much up and down as necessary staring from some intial configuration.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Hi there! Thank you so much for your answer, it really did help clarifying some aspects. Indeed, we have to use some kind of brute force, because we need to do it using backtracking algorithm. The thing is, we start exploring the 'tree' and picking student 1. Which student will pair it? Let's say 2 will. First pair is (1,2). Once we go through all, we should go back (backtracking) and try (1,3) as first pair. This goes on and on until we find the best solution. – alexhzr May 12 '19 at 14:11
  • @alexhzr I somehow got hooked up by this problem and couldnt resist to clean up my code a bit to post it ;) – 463035818_is_not_an_ai May 15 '19 at 21:47