列出两组拟合标准的所有配对的算法

Algorithm to list all pairings of two sets fitting criteria

我有 2 个有限集:

Set A: {A, B, C, D, ...}
Set B: {1, 2, 3, 4, ...}

它们可以是 相同或不同的长度。我正在尝试将它们配对,以便 A 组中的每个元素都恰好与一个元素配对集合 B.

中的元素

注意:反之亦然。 集合 B 中的一个元素可以与集合 A 中的零个、一个或所有元素配对。因此,这不是双射。

我已经尝试了几个小时来想出一个算法来列出符合这些规则的所有可能配对集。我已经尝试过迭代和递归,但似乎都没有太大的进展。

我也看过 this SO Q&A 但该解决方案对我不起作用,因为它不允许第二组中的一个元素与第一组中的多个元素配对。

任何方向将不胜感激!

P.S。万一有人指责我,这不是学校作业。这是我开始的个人项目。

两个有效配对的示例:

1:A
2:C
3:B
4:D

1:A,C
2:D
3:
4:B

编辑:回答!感谢@Vincent

注意:不小心颠倒了这里域(A组)和codomain(B组)的定义。现在,codomain 的每个元素恰好映射到域中的一个元素,但不一定相反。

import java.util.*;

//My third attempt at a solution :)
public class MapStuff3 {

    public static void main(String[] args) {
        //Sample domain
        String[] domainArr = { "A", "B", "C", "D" };
        List<String> domain = new ArrayList<String>() {

            {
                for (String s : domainArr)
                    add(s);
            }
        };

        //Sample codomain
        Integer[] codomainArr = { 1, 2, 3 };
        List<Integer> codomain = new ArrayList<Integer>() {

            {
                for (Integer i : codomainArr)
                    add(i);
            }
        };

        map(domain, codomain);
    }

    //Each of codomain maps to exactly one on domain, not necessarily the converse
    static <A, B> void map(List<A> domain, List<B> codomain) {
        //This is the list of all combinations
        List<Map<B, List<A>>> listOfAllMaps = new ArrayList<Map<B, List<A>>>();

        Map<B, List<A>> map = new HashMap<B, List<A>>();
        listOfAllMaps = map(domain, codomain, map);

        int count = 0; //just for fun. count will always go to codomain.size()^domain.size()
        ListIterator<Map<B, List<A>>> listIterator = listOfAllMaps.listIterator();
        while (listIterator.hasNext()) {
            Map<B, List<A>> oneMap = listIterator.next();

            //count the number of elements in all of the lists to make sure that every element in the domain is paired to something
            int totalSize = 0;
            for (B key : oneMap.keySet())
                totalSize += oneMap.get(key).size();

            //if it is, it's valid
            if (totalSize == domain.size())
                System.out.println(++count + ": " + oneMap);
            else //remove invalid entries
                listIterator.remove();
        }

        //Because we removed invalid entires, listOfAllMaps will now contain the correct answer
    }

    /**
     * Recursive method to list all possible mappings, including ones where not every element in the
     * domain is mapped to, layer by layer
     */
    static <A, B> List<Map<B, List<A>>> map(List<A> domain, List<B> codomain, Map<B, List<A>> map) {
        //Base case
        //If every element in the codomain has been mapped, return our map inside of a list
        if (codomain.size() == 0) {
            List<Map<B, List<A>>> finalAnswer = new ArrayList<Map<B, List<A>>>();
            finalAnswer.add(map);
            return finalAnswer;
        }

        //Holds our partial answers
        List<Map<B, List<A>>> listOfAllMaps = new ArrayList<Map<B, List<A>>>();

        //List of the indices of all subsets of the given domain
        List<List<Integer>> indexSuperList = listAll(domain.size());

        //For each subset of the given domain
        for (List<Integer> indexList : indexSuperList) {
            //Make copies of our parameters so they can be mutated without affecting everything else
            //I always forget if this is necessary in Java, but I'm too tired too look it up right now. Better safe than sorry.
            List<A> workingDomain = new ArrayList<A>(domain);
            List<B> workingCodomain = new ArrayList<B>(codomain);
            Map<B, List<A>> workingMap = new HashMap<B, List<A>>(map);

            //Map all elements in the given subset of the domain to the first element in the given codomain
            //Also remove every element in the domain that has been used
            workingMap.put(workingCodomain.get(0), new ArrayList<A>());
            for (int i : indexList)
                workingMap.get(workingCodomain.get(0)).add(workingDomain.remove(i));

            //Remove the first element in the given codomain
            workingCodomain.remove(0);

            //Add on the next layer
            listOfAllMaps.addAll(map(workingDomain, workingCodomain, workingMap));
        }

        return listOfAllMaps; //This will only happen if the next layer of recursion hit the base case. Just return what we have
    }

    /**
     * Lists the indices corresponding to all subsets of a list of a certain size N, including the
     * empty set
     * Adapted from 
     */
    static List<List<Integer>> listAll(int N) {
        List<List<Integer>> allLists = new ArrayList<List<Integer>>();
        allLists.add(new ArrayList<Integer>());
        int allMasks = (1 << N);
        for (int i = 1; i < allMasks; i++) {
            List<Integer> oneList = new ArrayList<Integer>();
            for (int j = 0; j < N; j++)
                if ((i & (1 << j)) > 0) //The j-th element is used
                    oneList.add(j);

            //Put in reverse order so that when we're removing them on lines 88-89, removing the first doesn't invalidate the indices of the others
            Collections.sort(oneList, Collections.reverseOrder());
            allLists.add(oneList);
        }
        return allLists;
    }

}

打印出来:

1: {1=[], 2=[], 3=[D, C, B, A]}
2: {1=[], 2=[A], 3=[D, C, B]}
3: {1=[], 2=[B], 3=[D, C, A]}
4: {1=[], 2=[B, A], 3=[D, C]}
5: {1=[], 2=[C], 3=[D, B, A]}
6: {1=[], 2=[C, A], 3=[D, B]}
7: {1=[], 2=[C, B], 3=[D, A]}
8: {1=[], 2=[C, B, A], 3=[D]}
9: {1=[], 2=[D], 3=[C, B, A]}
10: {1=[], 2=[D, A], 3=[C, B]}
11: {1=[], 2=[D, B], 3=[C, A]}
12: {1=[], 2=[D, B, A], 3=[C]}
13: {1=[], 2=[D, C], 3=[B, A]}
14: {1=[], 2=[D, C, A], 3=[B]}
15: {1=[], 2=[D, C, B], 3=[A]}
16: {1=[], 2=[D, C, B, A], 3=[]}
17: {1=[A], 2=[], 3=[D, C, B]}
18: {1=[A], 2=[B], 3=[D, C]}
19: {1=[A], 2=[C], 3=[D, B]}
20: {1=[A], 2=[C, B], 3=[D]}
21: {1=[A], 2=[D], 3=[C, B]}
22: {1=[A], 2=[D, B], 3=[C]}
23: {1=[A], 2=[D, C], 3=[B]}
24: {1=[A], 2=[D, C, B], 3=[]}
25: {1=[B], 2=[], 3=[D, C, A]}
26: {1=[B], 2=[A], 3=[D, C]}
27: {1=[B], 2=[C], 3=[D, A]}
28: {1=[B], 2=[C, A], 3=[D]}
29: {1=[B], 2=[D], 3=[C, A]}
30: {1=[B], 2=[D, A], 3=[C]}
31: {1=[B], 2=[D, C], 3=[A]}
32: {1=[B], 2=[D, C, A], 3=[]}
33: {1=[B, A], 2=[], 3=[D, C]}
34: {1=[B, A], 2=[C], 3=[D]}
35: {1=[B, A], 2=[D], 3=[C]}
36: {1=[B, A], 2=[D, C], 3=[]}
37: {1=[C], 2=[], 3=[D, B, A]}
38: {1=[C], 2=[A], 3=[D, B]}
39: {1=[C], 2=[B], 3=[D, A]}
40: {1=[C], 2=[B, A], 3=[D]}
41: {1=[C], 2=[D], 3=[B, A]}
42: {1=[C], 2=[D, A], 3=[B]}
43: {1=[C], 2=[D, B], 3=[A]}
44: {1=[C], 2=[D, B, A], 3=[]}
45: {1=[C, A], 2=[], 3=[D, B]}
46: {1=[C, A], 2=[B], 3=[D]}
47: {1=[C, A], 2=[D], 3=[B]}
48: {1=[C, A], 2=[D, B], 3=[]}
49: {1=[C, B], 2=[], 3=[D, A]}
50: {1=[C, B], 2=[A], 3=[D]}
51: {1=[C, B], 2=[D], 3=[A]}
52: {1=[C, B], 2=[D, A], 3=[]}
53: {1=[C, B, A], 2=[], 3=[D]}
54: {1=[C, B, A], 2=[D], 3=[]}
55: {1=[D], 2=[], 3=[C, B, A]}
56: {1=[D], 2=[A], 3=[C, B]}
57: {1=[D], 2=[B], 3=[C, A]}
58: {1=[D], 2=[B, A], 3=[C]}
59: {1=[D], 2=[C], 3=[B, A]}
60: {1=[D], 2=[C, A], 3=[B]}
61: {1=[D], 2=[C, B], 3=[A]}
62: {1=[D], 2=[C, B, A], 3=[]}
63: {1=[D, A], 2=[], 3=[C, B]}
64: {1=[D, A], 2=[B], 3=[C]}
65: {1=[D, A], 2=[C], 3=[B]}
66: {1=[D, A], 2=[C, B], 3=[]}
67: {1=[D, B], 2=[], 3=[C, A]}
68: {1=[D, B], 2=[A], 3=[C]}
69: {1=[D, B], 2=[C], 3=[A]}
70: {1=[D, B], 2=[C, A], 3=[]}
71: {1=[D, B, A], 2=[], 3=[C]}
72: {1=[D, B, A], 2=[C], 3=[]}
73: {1=[D, C], 2=[], 3=[B, A]}
74: {1=[D, C], 2=[A], 3=[B]}
75: {1=[D, C], 2=[B], 3=[A]}
76: {1=[D, C], 2=[B, A], 3=[]}
77: {1=[D, C, A], 2=[], 3=[B]}
78: {1=[D, C, A], 2=[B], 3=[]}
79: {1=[D, C, B], 2=[], 3=[A]}
80: {1=[D, C, B], 2=[A], 3=[]}
81: {1=[D, C, B, A], 2=[], 3=[]}

我建议你在集合 A 上使用递归处理:枚举所有可以在第一个元素上完成的配对,并将其与集合 A 的其余部分和集合 B 的其余部分的枚举相结合。

在集合 A 的递归结束时,您可以像在问题中所做的那样按集合 B 中的元素对对进行分组。

看起来您正在寻找从 SetASetB 的所有函数。这是一个示例 Python 解决方案。

from itertools import combinations_with_replacement

SetA = {'A', 'B', 'C', 'D'}
SetB = {1, 2, 3, 4, 5}

res = (list(zip(SetA, comb))
        for comb in combinations_with_replacement(SetB, len(SetA)))

编辑:我指的是元组集合意义上的函数。

有没有更简单的方法?

在每个配对中,A 的每个元素都映射到 B 中的一个元素。因此,我们需要从 |B| 索引生成 |A| 事物的所有排列。会有|B|^|A|这样的配对,其中^是幂运算符,||表示集合的大小

这里有一些非常简单的代码来说明,我们只是使用 Strings 来收集 A 的元素分配给 B.

的元素
public class Pairings
{
    public static void main(String[] args)
    {
        String[] a = { "A", "B", "C", "D" };
        String[] b = { "1", "2", "3" };
        pairs(a, b);
    }

    static void pairs(String[] a, String[] b)
    {
        int asize = a.length;
        int bsize = b.length;
        int[] idx = new int[asize];

        int c = 1;
        String[] pairings = new String[bsize];
        while (true)
        {
            // gather the pairings of a to b
            Arrays.fill(pairings, "");
            for (int i = 0; i < asize; i++)
            {
                pairings[idx[i]] += a[i] + " ";
            }

            System.out.print(c++ + ": ");
            for (int i = 0; i < bsize; i++)
            {
                if (!pairings[i].isEmpty())
                    pairings[i] = pairings[i].substring(0, pairings[i].length() - 1);
                System.out.print(b[i] + "=[" + pairings[i] + "] ");
            }
            System.out.println();

            // generate the next permutation
            int k = idx.length - 1;
            for (; k >= 0; k--)
            {
                idx[k]++;
                if (idx[k] < bsize) break;                  
                idx[k] = 0;
            }

            // if the first index wrapped around then we're done
            if (k < 0) break;               
        }
    }
}

输出:

1: 1=[A B C D] 2=[] 3=[] 
2: 1=[A B C] 2=[D] 3=[] 
3: 1=[A B C] 2=[] 3=[D] 
4: 1=[A B D] 2=[C] 3=[] 
5: 1=[A B] 2=[C D] 3=[] 
6: 1=[A B] 2=[C] 3=[D] 
7: 1=[A B D] 2=[] 3=[C] 
8: 1=[A B] 2=[D] 3=[C] 
9: 1=[A B] 2=[] 3=[C D] 
.
.
.
74: 1=[C] 2=[D] 3=[A B] 
75: 1=[C] 2=[] 3=[A B D] 
76: 1=[D] 2=[C] 3=[A B] 
77: 1=[] 2=[C D] 3=[A B] 
78: 1=[] 2=[C] 3=[A B D] 
79: 1=[D] 2=[] 3=[A B C] 
80: 1=[] 2=[D] 3=[A B C] 
81: 1=[] 2=[] 3=[A B C D]