1

我目前正在努力为我的配对系统编写算法。对于对数学一无所知的人来说,这可能是孩子们的游戏,好吧,我不知道。

让我们看下面的例子:

一个大厅正好由8 名玩家组成。
队列中有 9 方。派对规模为:1、6、3、8、2、1、5、3、2
。算法需要从这些派对中创建尽可能多的完整大厅。

可能只有满员的大厅。因此,可能存在无法匹配的各方。然后,那些将等待新的政党进入队列。

我当前的代码确实将各方与大厅匹配,但它缺乏找到最佳组合以找到尽可能多的完整大厅的功能。

示例:大厅大小正好为 7,派对:2,2,4,3,1,5 -> 2,5 和 4,3 可以匹配,但是 2,2,3 和 4,1,5 不能匹配被匹配。

我很感激任何帮助。

我当前的代码:

    // Limitation: Is not intelligent. Example: Size 7, Teams 2,2,4,3,1,5 -> 2,5 and 4,3 could be matched, but it will do 2,2,3 and 4,1,5
    // Possible solution: Sort for size or match parties with lowest possible match e.g try 5,1 then 5,2 ...
    public List<MatchedParty> getLobbies(List<Party> parties, int teamSize) {
        List<MatchedParty> lobbies = new ArrayList<>();

        parties.sort(Comparator.comparing(Party::getJoinedQueueTime));

        while (!(parties.isEmpty())) {
            MatchedParty matchedParty = new MatchedParty();
            List<Party> team1 = new ArrayList<>();
            List<Party> team2 = new ArrayList<>();

            boolean teamReset = false;
            int counter = 0;
            while (team1.stream().mapToLong(c -> c.getMembers().size()).sum() != teamSize || team2.stream().mapToLong(c -> c.getMembers().size()).sum() != teamSize) {

                if (team1.stream().mapToLong(c -> c.getMembers().size()).sum() != teamSize) {
                    team1.clear();
                }

                if (team2.stream().mapToLong(c -> c.getMembers().size()).sum() != teamSize) {
                    team2.clear();
                }

                // Iterate over all parties and add matching ones to the teams
                for (int i = counter; i < parties.size(); i++) {
                    if (team1.stream().mapToLong(c -> c.getMembers().size()).sum() + parties.get(i).getMembers().size() <= teamSize && !(team2.contains(parties.get(i)))) {
                        team1.add(parties.get(i));
                    } else if (team2.stream().mapToLong(c -> c.getMembers().size()).sum() + parties.get(i).getMembers().size() <= teamSize && !(team1.contains(parties.get(i)))) {
                        team2.add(parties.get(i));
                    }
                }

                // Reset search when first team is full
                if ((team1.stream().mapToLong(c -> c.getMembers().size()).sum() == teamSize || team2.stream().mapToLong(c -> c.getMembers().size()).sum() == teamSize) && !(teamReset)) {
                    counter = 0;
                    teamReset = true;
                }

                // Check if we iterated over all parties, if so exit the loop
                if (counter <= parties.size()) {
                    counter++;
                } else {
                    break;
                }
            }

            // Are there still teams found? If not, return the lobbies
            if (team1.stream().mapToLong(c -> c.getMembers().size()).sum() == teamSize && team2.stream().mapToLong(c -> c.getMembers().size()).sum() == teamSize) {
                matchedParty.setTeam1(team1);
                matchedParty.setTeam2(team2);
                lobbies.add(matchedParty);
                parties.removeAll(team1);
                parties.removeAll(team2);
            } else {
                return lobbies;
            }
        }

        // Maybe all parties found a team, if so return too
        return lobbies;
    }

4

2 回答 2

1

您的算法通常是正确的方法,但您可以做一项改进。由于您只关心对给定的政党规模进行精确匹配,因此我会将每一方映射到一个包含给定规模的所有政党的列表:IE:{1: [parties of size 1], 2: [parties of size 2], ...}

然后看看这个:

https://en.wikipedia.org/wiki/Partition_%28number_theory%29

棘手的一点是我们希望尽可能理想地匹配事物。基本上,我们希望从最少数量的参与方开始,到需要合并的参与方数量最多。IE 用于 8 人的聚会大小:8, 7 + 1, 6 + 2, 5 + 3依此类推。一旦这些都匹配好了,那么我们看看需要组合 3 个部分的那些(总是按从多到少的顺序):6 + 1 + 1, 5 + 2 + 1, 4 + 2 + 2...然后是 4 个部分,然后是 5 个部分、6 个部分、7 个部分,最后是 8 个部分(全部那些)。

看起来有 8 个分区的 22 个分区,您可能只是对它们进行硬编码并循环它们。根据您的最大参与方数量,您可以为您需要的所有参与方数量构建一个分区表。

这大致是该算法在您的示例列表中的工作方式:

1, 6, 3, 8, 2, 1, 5, 3, 2

{party size: number of parties remaining}
{8: 1, 6: 1, 5: 1, 3: 2, 2: 2, 1: 1}
{6: 1, 5: 1, 3: 2, 2: 2, 1: 1} => (8)
{5: 1, 3: 2, 2: 1, 1: 1} => (8), (6,2)
{3: 1, 2: 1, 1: 1} => (8), (6,2), (5,3)
{3: 1, 2: 1, 1: 1}

如果您密切关注剩余方的总数,您将停止检查有3 + 2 + 1 = 6 < 8,因此您无法创建任何其他有效方。我相信这创造了政党的想法。

您打算举办 7 人聚会的示例:

2,2,4,3,1,5
{5: 1, 4: 1, 3: 1, 2: 2}
{4: 1, 3: 1, 2: 1} => (5,2),
{2: 1} => (5,2),(4,3)

在这一点上,再次举办派对是不可能的7

至于生成分区,这似乎很可靠:

https://www.geeksforgeeks.org/generate-unique-partitions-of-an-integer/

是否需要优化取决于最大派对规模。16231分区,但328349不错,但是641741630,这可能很糟糕,除非你有很多聚会。 http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Partitions/partitions.html#pcalc1

编辑:这里唯一的问题是小党可能处于不利地位。在这种情况下,您可以颠倒搜索顺序,因此它开始查看最小尺寸的派对(所有派对)而不是最小尺寸(全派对)。每次说 3-10 方搜索时,我可能会颠倒搜索顺序。取决于你做的频率。

您可能还想尝试两个方向,然后只选择结果更好的方向。

以相反的顺序:

1, 6, 3, 8, 2, 1, 5, 3, 2
{8: 1, 6: 1, 5: 1, 3: 2, 2: 2, 1: 1}
{8: 1, 6: 1, 5: 1, 3: 1} => (1,2,2,3)
{6: 1} => (1,2,2,3),(3,5),(8)

虽然在这种情况下,两种方式都创建了 3 个完整的组,但我怀疑这将永远是这种情况。但是,请注意,这种方法确实将其减少到只有 6 人中的 1 人,而不是 3、2 和 1 人。

这种方法基本上是为了尽可能减少参与方的数量。另一种方法旨在最大化完整组的数量。两者都有其用途并建议您同时使用两者,只是您使用一个与另一个的频率的问题。

嗯,第三种选择可能会从双方攻击它,但在这种情况下,你还有其他问题,它对中间的人有偏见。

1, 6, 3, 8, 2, 1, 5, 3, 2
{8: 1, 6: 1, 5: 1, 3: 2, 2: 2, 1: 1}
{6: 1, 5: 1, 3: 2, 2: 2, 1: 1} => (8)
{6: 1, 5: 1, 3: 1} => (8),(1,2,2,3)
{6: 1} => (8),(1,2,2,3),(5,3)

实际上,如果您想让事情变得更有趣,您也可以随机打乱所有分区并以这种方式运行算法,尽管怀疑这会产生高于平均水平的结果。要点是您需要查看 N 的整数分区。

于 2020-10-10T04:49:56.457 回答
0

因此,在阅读了评论者的几个链接之后,@Andreas 的答案似乎是最正确的。似乎我的问题是装箱问题的变体,只是在我的情况下,我确实要求只装满垃圾箱。

我的问题主要可以通过使用我已经在问题中发布的算法来解决,但是将所有条目从最高到最低排序,并且在填充大厅(垃圾箱)时,首先插入最大的条目。

于 2020-10-08T14:43:04.507 回答