这是一个相当有效的算法。在小型数据集上没问题,但对于较大的数据集则需要时间和 RAM。我不会声称它是最好的算法,但它肯定比蛮力检查每个好坏配对要好得多。
关键的见解是,我们不需要查看个人的好坏配对,我们可以查看整个团队的好坏总分。让两个有效团队的分数为 (X, A) 和 (Y, B)。即X是1队所有好球员的总分,A是1队所有坏球员的总分。同理,Y是2队好球员的总分,B是1队总分。那支球队中的坏球员。那么
X - A = Y - B
X + B = Y + A
2(X + B) = X + B + Y + A
X + B = (X + B + Y + A) / 2
Let M = (X + Y + A + B) / 2
Thus B = M - X
所以我们只需要计算 M,即所有玩家得分的总和,然后对于每个好玩家分区 X,我们需要查看是否有匹配的坏玩家分区 B。(顺便说一下,上面的算术表明 X + Y + A + B 必须是偶数才能存在解,因为 B & X 必须是整数)。
为了以合理的效率做到这一点,我们创建了一个坏播放器分区的字典。 dict 键是该分区的总分,关联的值是具有该分数的所有坏玩家分区的列表。
然后我们遍历好的播放器分区对。我们找到一对中第一个分区的总分,从 M 中减去它以获得所需的坏分,并查看该坏分是否在字典中。如果是,我们已经找到了解决方案。
equal_parts 函数是我知道的在纯 Python 中将列表拆分为所有等长分区的最快方法,有关详细信息,请参阅我几年前写的 this answer。
下面的代码为问题中给出的数据找到了一个单一的解决方案;该解决方案为每个团队提供 2 个选项。我已经包含了一些额外的代码(目前已被注释掉)来生成一些随机的假数据,以便在更大的数据集上测试代码。
更新
上一个版本的逻辑有一个小错误。当我找到总分正确的坏分区列表b1 时,我使用bad_total - b1 为其他团队获取坏分区的补充列表。但这并不总是能正常工作。 :oops: 新版本使用bad_parts 字典,将每个坏分区存储为键和值,这样可以轻松获得正确的互补分区。
此外,旧的make_pairs 函数有点草率,因此它可以生成共享球员的 Team 1 列表和 Team 2 列表。 :其他哎呀:。我们现在有了一个新函数make_teams,它调用了一个已修复的make_pairs。 make_teams 函数按部分打印不同的 Team 1 和 Team 2 排列。在每个部分中,没有 Team 1 列表与 Team 2 列表共享玩家。
我还做了一些其他的小改动,主要是分区元组中的名称总是按字母顺序排序。这使得它们作为字典键的使用更加健壮。我还稍微调整了输出格式。
有时可能会发生(例如,在您在评论中提供的数据的解决方案 23 中)一个所谓的解决方案不起作用:没有可以将坏玩家与好玩家组合而不会产生负分的排列。希望这不是一个大问题。为了让代码自动处理这个问题,我们需要改变它,而不是打印结果,而是需要将结果存储在列表中(或者可能使用生成器),然后测试结果列表不是在打印该解决方案之前清空。这会消耗更多的 RAM,并且会使逻辑比现在更加混乱。
from itertools import combinations, permutations
from collections import defaultdict
from random import seed, randrange
seed(37)
def equal_parts(lst):
''' yield all equal-sized pair partitions of lst '''
first = lst[0]
allset_diff = set(lst).difference
for left in combinations(lst, len(lst) // 2):
if left[0] != first:
break
yield left, tuple(sorted(allset_diff(left)))
def find_partitions(bad, good):
print('bad', bad, len(bad))
print('good', good, len(good))
bad_total = sum(bad.values())
good_total = sum(good.values())
total = bad_total + good_total
if total % 2 != 0:
print('No solutions!')
return
magic = total // 2
print('magic =', magic)
# Create a dict of the good partition pairs
good_parts = dict(equal_parts(sorted(good)))
# Create a dict of the bad partition pairs, with each partition of
# the pair as a key and the complementary partiton as the value
bad_parts = {}
for bpart1, bpart2 in equal_parts(sorted(bad)):
bad_parts[bpart1] = bpart2
bad_parts[bpart2] = bpart1
#print(bad_parts)
# Get the sums of all the bad partitions, and save them in
# a dict of lists with the partition sum as the key and
# the partition in the value list
bad_sums = defaultdict(list)
for bpart in bad_parts:
s = sum(bad[k] for k in bpart)
bad_sums[s].append(bpart)
bad_sums = dict(bad_sums)
#print(bad_sums)
# Sum the 1st of each pair of good partitions & see if there's a matching bad partition
count = 0
for gpart1, gpart2 in good_parts.items():
g1 = sum(good[k] for k in gpart1)
b1 = magic - g1
if b1 in bad_sums:
count += 1
print('SOLUTION', count)
g2 = good_total - g1
b2 = bad_total - b1
blist1 = bad_sums[b1]
# Create the complementary list of bad partitions
blist2 = [bad_parts[k] for k in blist1]
tot1 = g1 - b2
tot2 = g2 - b1
print(gpart1, g1, '-', blist2, b2, '=', tot1)
print(gpart2, g2, '-', blist1, b1, '=', tot2, '\n')
make_teams(gpart1, gpart2, blist1, blist2)
def make_pairs(gpart, bpart):
for b in permutations(bpart):
total = 0
team = []
for gkey, bkey in zip(gpart, b):
score = good[gkey] - bad[bkey]
if score <= 0:
# Invalid pairing
break
total += score
team.append('{}-{}={}'.format(gkey, bkey, score))
else:
print(', '.join(team), total)
def make_teams(gpart1, gpart2, blist1, blist2):
section = 0
for bpart2, bpart1 in zip(blist2, blist1):
section += 1
print('Section', section)
print('Team 1:', ' '.join(gpart1), '+', ' '.join(bpart2))
make_pairs(gpart1, bpart2)
print('\nTeam 2:', ' '.join(gpart2), '+', ' '.join(bpart1))
make_pairs(gpart2, bpart1)
print()
# Make some fake data
def make_data(letters, lo, hi):
return {s: randrange(lo, hi) for s in letters}
#while True:
#bad = make_data('ZYXWVU', 1, 15)
#good = make_data('ABCDEF', 10, 30)
#bad_total = sum(bad.values())
#good_total = sum(good.values())
#if bad_total % 2 == good_total % 2:
#break
bad = {'A': 13, 'B': 6, 'C': 9, 'D': 2,}
good = {'X': 25, 'Y': 16, 'Z': 17, 'K': 10,}
#bad = {'bA': 7, 'bB': 10, 'bC': 2, 'bD': 12, 'bE': 15, 'bF': 14, 'bG': 17, 'bH': 15}
#good = {'gA': 19, 'gB': 36, 'gC': 9, 'gD': 15, 'gE': 24, 'gF': 23, 'gG': 24, 'gH': 24}
find_partitions(bad, good)
输出
bad {'A': 13, 'B': 6, 'C': 9, 'D': 2} 4
good {'X': 25, 'Y': 16, 'Z': 17, 'K': 10} 4
magic = 49
SOLUTION 1
('K', 'Z') 27 - [('B', 'D')] 8 = 19
('X', 'Y') 41 - [('A', 'C')] 22 = 19
Section 1
Team 1: K Z + B D
K-B=4, Z-D=15 19
K-D=8, Z-B=11 19
Team 2: X Y + A C
X-A=12, Y-C=7 19
X-C=16, Y-A=3 19
这是假数据的输出。
bad {'Z': 2, 'Y': 9, 'X': 5, 'W': 6, 'V': 11, 'U': 12} 6
good {'A': 23, 'B': 28, 'C': 28, 'D': 21, 'E': 28, 'F': 11} 6
magic = 92
SOLUTION 1
('A', 'B', 'C') 79 - [('U', 'V', 'Y')] 32 = 47
('D', 'E', 'F') 60 - [('W', 'X', 'Z')] 13 = 47
Section 1
Team 1: A B C + U V Y
A-U=11, B-V=17, C-Y=19 47
A-U=11, B-Y=19, C-V=17 47
A-V=12, B-U=16, C-Y=19 47
A-V=12, B-Y=19, C-U=16 47
A-Y=14, B-U=16, C-V=17 47
A-Y=14, B-V=17, C-U=16 47
Team 2: D E F + W X Z
D-W=15, E-X=23, F-Z=9 47
D-W=15, E-Z=26, F-X=6 47
D-X=16, E-W=22, F-Z=9 47
D-X=16, E-Z=26, F-W=5 47
D-Z=19, E-W=22, F-X=6 47
D-Z=19, E-X=23, F-W=5 47
SOLUTION 2
('A', 'B', 'D') 72 - [('U', 'V', 'Z'), ('V', 'X', 'Y')] 25 = 47
('C', 'E', 'F') 67 - [('W', 'X', 'Y'), ('U', 'W', 'Z')] 20 = 47
Section 1
Team 1: A B D + U V Z
A-U=11, B-V=17, D-Z=19 47
A-U=11, B-Z=26, D-V=10 47
A-V=12, B-U=16, D-Z=19 47
A-V=12, B-Z=26, D-U=9 47
A-Z=21, B-U=16, D-V=10 47
A-Z=21, B-V=17, D-U=9 47
Team 2: C E F + W X Y
C-W=22, E-X=23, F-Y=2 47
C-W=22, E-Y=19, F-X=6 47
C-X=23, E-W=22, F-Y=2 47
C-X=23, E-Y=19, F-W=5 47
C-Y=19, E-W=22, F-X=6 47
C-Y=19, E-X=23, F-W=5 47
Section 2
Team 1: A B D + V X Y
A-V=12, B-X=23, D-Y=12 47
A-V=12, B-Y=19, D-X=16 47
A-X=18, B-V=17, D-Y=12 47
A-X=18, B-Y=19, D-V=10 47
A-Y=14, B-V=17, D-X=16 47
A-Y=14, B-X=23, D-V=10 47
Team 2: C E F + U W Z
C-U=16, E-W=22, F-Z=9 47
C-U=16, E-Z=26, F-W=5 47
C-W=22, E-U=16, F-Z=9 47
C-Z=26, E-U=16, F-W=5 47
SOLUTION 3
('A', 'B', 'E') 79 - [('U', 'V', 'Y')] 32 = 47
('C', 'D', 'F') 60 - [('W', 'X', 'Z')] 13 = 47
Section 1
Team 1: A B E + U V Y
A-U=11, B-V=17, E-Y=19 47
A-U=11, B-Y=19, E-V=17 47
A-V=12, B-U=16, E-Y=19 47
A-V=12, B-Y=19, E-U=16 47
A-Y=14, B-U=16, E-V=17 47
A-Y=14, B-V=17, E-U=16 47
Team 2: C D F + W X Z
C-W=22, D-X=16, F-Z=9 47
C-W=22, D-Z=19, F-X=6 47
C-X=23, D-W=15, F-Z=9 47
C-X=23, D-Z=19, F-W=5 47
C-Z=26, D-W=15, F-X=6 47
C-Z=26, D-X=16, F-W=5 47
SOLUTION 4
('A', 'C', 'D') 72 - [('U', 'V', 'Z'), ('V', 'X', 'Y')] 25 = 47
('B', 'E', 'F') 67 - [('W', 'X', 'Y'), ('U', 'W', 'Z')] 20 = 47
Section 1
Team 1: A C D + U V Z
A-U=11, C-V=17, D-Z=19 47
A-U=11, C-Z=26, D-V=10 47
A-V=12, C-U=16, D-Z=19 47
A-V=12, C-Z=26, D-U=9 47
A-Z=21, C-U=16, D-V=10 47
A-Z=21, C-V=17, D-U=9 47
Team 2: B E F + W X Y
B-W=22, E-X=23, F-Y=2 47
B-W=22, E-Y=19, F-X=6 47
B-X=23, E-W=22, F-Y=2 47
B-X=23, E-Y=19, F-W=5 47
B-Y=19, E-W=22, F-X=6 47
B-Y=19, E-X=23, F-W=5 47
Section 2
Team 1: A C D + V X Y
A-V=12, C-X=23, D-Y=12 47
A-V=12, C-Y=19, D-X=16 47
A-X=18, C-V=17, D-Y=12 47
A-X=18, C-Y=19, D-V=10 47
A-Y=14, C-V=17, D-X=16 47
A-Y=14, C-X=23, D-V=10 47
Team 2: B E F + U W Z
B-U=16, E-W=22, F-Z=9 47
B-U=16, E-Z=26, F-W=5 47
B-W=22, E-U=16, F-Z=9 47
B-Z=26, E-U=16, F-W=5 47
SOLUTION 5
('A', 'C', 'E') 79 - [('U', 'V', 'Y')] 32 = 47
('B', 'D', 'F') 60 - [('W', 'X', 'Z')] 13 = 47
Section 1
Team 1: A C E + U V Y
A-U=11, C-V=17, E-Y=19 47
A-U=11, C-Y=19, E-V=17 47
A-V=12, C-U=16, E-Y=19 47
A-V=12, C-Y=19, E-U=16 47
A-Y=14, C-U=16, E-V=17 47
A-Y=14, C-V=17, E-U=16 47
Team 2: B D F + W X Z
B-W=22, D-X=16, F-Z=9 47
B-W=22, D-Z=19, F-X=6 47
B-X=23, D-W=15, F-Z=9 47
B-X=23, D-Z=19, F-W=5 47
B-Z=26, D-W=15, F-X=6 47
B-Z=26, D-X=16, F-W=5 47
SOLUTION 6
('A', 'D', 'E') 72 - [('U', 'V', 'Z'), ('V', 'X', 'Y')] 25 = 47
('B', 'C', 'F') 67 - [('W', 'X', 'Y'), ('U', 'W', 'Z')] 20 = 47
Section 1
Team 1: A D E + U V Z
A-U=11, D-V=10, E-Z=26 47
A-U=11, D-Z=19, E-V=17 47
A-V=12, D-U=9, E-Z=26 47
A-V=12, D-Z=19, E-U=16 47
A-Z=21, D-U=9, E-V=17 47
A-Z=21, D-V=10, E-U=16 47
Team 2: B C F + W X Y
B-W=22, C-X=23, F-Y=2 47
B-W=22, C-Y=19, F-X=6 47
B-X=23, C-W=22, F-Y=2 47
B-X=23, C-Y=19, F-W=5 47
B-Y=19, C-W=22, F-X=6 47
B-Y=19, C-X=23, F-W=5 47
Section 2
Team 1: A D E + V X Y
A-V=12, D-X=16, E-Y=19 47
A-V=12, D-Y=12, E-X=23 47
A-X=18, D-V=10, E-Y=19 47
A-X=18, D-Y=12, E-V=17 47
A-Y=14, D-V=10, E-X=23 47
A-Y=14, D-X=16, E-V=17 47
Team 2: B C F + U W Z
B-U=16, C-W=22, F-Z=9 47
B-U=16, C-Z=26, F-W=5 47
B-W=22, C-U=16, F-Z=9 47
B-Z=26, C-U=16, F-W=5 47
附言。我想到了一种方法来快速处理更多 更大的数据集。它不会找到所有解决方案,但它应该找到很多解决方案。这个想法是将大数据集分成几个较小的集合,独立解决这些较小的集合,然后组合解决方案。唯一棘手的部分是进行除法,以便每个较小的集合都是可解的。所以在每组bad_total < good_total 和bad_total%2 == good_total%2 中。在尝试划分之前按分数对完整数据集进行排序可能是有意义的,以便每个较小数据集的坏组和好组大致匹配。