繁体   English   中英

如何以编程方式将单圈时间分组为团队,以最大程度地减少差异?

[英]How to programatically group lap times into teams to minimize difference?

给定以下(任意)单圈时间:

John: 47.20
Mark: 51.14
Shellie: 49.95
Scott: 48.80
Jack: 46.60
Cheryl: 52.70
Martin: 57.65
Karl: 55.45
Yong: 52.30
Lynetta: 59.90
Sueann: 49.24
Tempie: 47.88
Mack: 51.11
Kecia: 53.20
Jayson: 48.90
Sanjuanita: 45.90
Rosita: 54.43
Lyndia: 52.38
Deloris: 49.90
Sophie: 44.31
Fleta: 58.12
Tai: 61.23
Cassaundra: 49.38 
Oren: 48.39

我们正在参加卡丁车耐力赛,而这个想法(而不是让团队选择)是编写一种工具来处理初始排位赛时间,然后吐出最匹配的分组。

我的初步调查使我感到这是一种集团化的绘图类型情况,但是我从未使用过绘图算法,所以我觉得自己不那么深入。

什么是最快/最简单的方法来产生三个人的组,它们具有最接近的平均平均圈速,以消除他们之间的整体优势/差异?

这是我可以使用networkx来实现的东西吗?如果可以,那么根据上述数据集,如何最好地定义图?

如果我理解正确,只需对时间列表进行排序,然后将前三个,后三个,直到前三个进行分组。

编辑:我不正确理解

因此,我们的想法是将N个人组成N / 3个团队,使平均N / 3个团队(而不是我误认为每个团队中的3个人)的平均时间尽可能近。 在这种情况下,我认为您仍然可以从对N个驱动程序进行降序排序开始。 然后,初始化N / 3个团队的空列表。 然后,按照每圈时间的递减顺序,将每个车手分配给当前总圈时间最小的车队(或平局时,选择其中一个车队)。 这是一种简单的装箱算法的变体。

这是一个简单的Python实现:

times = [47.20, 51.14, 49.95, 48.80, 46.60, 52.70, 57.65, 55.45, 52.30, 59.90, 49.24, 47.88, 51.11, 53.20, 48.90, 45.90, 54.43, 52.38, 49.90, 44.31, 58.12, 61.23, 49.38, 48.39]

Nteams = len(times)/3
team_times = [0] * Nteams
team_members = [[]] * Nteams

times = sorted(times,reverse=True)
for m in range(len(times)):
    i = team_times.index(min(team_times))
    team_times[i] += times[m]
    team_members[i] = team_members[i] + [m]

for i in range(len(team_times)):
    print(str(team_members[i]) + ": avg time " + str(round(team_times[i]/3,3)))

其输出是

[0, 15, 23]: avg time 51.593
[1, 14, 22]: avg time 51.727
[2, 13, 21]: avg time 51.54
[3, 12, 20]: avg time 51.6
[4, 11, 19]: avg time 51.48
[5, 10, 18]: avg time 51.32
[6, 9, 17]: avg time 51.433
[7, 8, 16]: avg time 51.327

(请注意,团队成员编号是按照圈速从0开始的降序而不是其原始顺序来引用他们的)。

这样做的一个问题是,如果时间变化很大,就没有严格的限制可以使每个团队中的球员人数准确地达到3。但是,就您的目的而言,如果可以使接力赛接近,那也许还可以,并且可能时间差远小于平均时间的情况很少发生。

编辑如果在所有情况下您只希望每支球队有3名球员,那么可以对代码进行微不足道的修改,以便在每一步中找到总圈时间最少且尚未分配3名球员的球队。 这需要对主代码块进行一些小的修改:

times = sorted(times,reverse=True)
for m in range(len(times)):
    idx = -1
    for i in range(Nteams):
        if len(team_members[i]) < 3:
            if (idx == -1) or (team_times[i] < team_times[idx]):
                idx = i
    team_times[idx] += times[m]
    team_members[idx] = team_members[idx] + [m]

对于问题中的示例问题,上述解决方案当然是相同的,因为它没有尝试让每个团队容纳不超过3名球员。

当您遇到这样的问题时,一种方法总是利用随机性。

虽然其他人说他们认为 X或Y应该起作用,但我知道我的算法至少会收敛到局部最大值。 如果您可以证明可以通过成对交换(状态属性对于例如“旅行销售员问题”而言是正确的)可以从任何其他状态空间获得任何状态空间,则该算法将找到全局最优值(给定时间)。

此外,该算法尝试使各个组中平均时间的标准偏差最小化,因此它提供了一个自然的指标来衡量您得到的答案的好坏:即使结果不精确,获得的标准偏差也为0.058。可能距离您的目的足够近。

换句话说,可能有一个精确的解决方案,但是随机解决方案通常很容易想象,无需花费很长时间进行编码,可以很好地收敛,并且能够产生可接受的答案。

#!/usr/bin/env python3

import numpy as np
import copy
import random

data = [
  (47.20,"John"),
  (51.14,"Mark"),
  (49.95,"Shellie"),
  (48.80,"Scott"),
  (46.60,"Jack"),
  (52.70,"Cheryl"),
  (57.65,"Martin"),
  (55.45,"Karl"),
  (52.30,"Yong"),
  (59.90,"Lynetta"),
  (49.24,"Sueann"),
  (47.88,"Tempie"),
  (51.11,"Mack"),
  (53.20,"Kecia"),
  (48.90,"Jayson"),
  (45.90,"Sanjuanita"),
  (54.43,"Rosita"),
  (52.38,"Lyndia"),
  (49.90,"Deloris"),
  (44.31,"Sophie"),
  (58.12,"Fleta"),
  (61.23,"Tai"),
  (49.38 ,"Cassaundra"),
  (48.39,"Oren")
]

#Divide into initial groupings
NUM_GROUPS = 8
groups = []
for x in range(NUM_GROUPS): #Number of groups desired
  groups.append(data[x*len(data)//NUM_GROUPS:(x+1)*len(data)//NUM_GROUPS])

#Ensure all groups have the same number of members
assert all(len(groups[0])==len(x) for x in groups)

#Get average time of a single group
def FitnessGroup(group): 
  return np.average([x[0] for x in group])

#Get standard deviation of all groups' average times
def Fitness(groups):
  avgtimes = [FitnessGroup(x) for x in groups] #Get all average times
  return np.std(avgtimes) #Return standard deviation of average times

#Initially, the best grouping is just the data
bestgroups  = copy.deepcopy(groups)
bestfitness = Fitness(groups)

#Generate mutations of the best grouping by swapping two randomly chosen members
#between their groups
for x in range(10000): #Run a large number of times
  groups = copy.deepcopy(bestgroups)       #Always start from the best grouping
  g1 = random.randint(0,len(groups)-1)     #Choose a random group A
  g2 = random.randint(0,len(groups)-1)     #Choose a random group B
  m1 = random.randint(0,len(groups[g1])-1) #Choose a random member from group A
  m2 = random.randint(0,len(groups[g2])-1) #Choose a random member from group B
  groups[g1][m1], groups[g2][m2] = groups[g2][m2], groups[g1][m1] #Swap 'em
  fitness = Fitness(groups)                #Calculate fitness of new grouping
  if fitness<bestfitness:                  #Is it a better fitness?
    bestfitness = fitness                  #Save fitness
    bestgroups  = copy.deepcopy(groups)    #Save grouping

#Print the results
for g in bestgroups:
  for m in g:
    print("{0:15}".format(m[1]), end='') 
  print("{0:15.3f}".format(FitnessGroup(g)), end='')
  print("")
print("Standard deviation of teams: {0:.3f}".format(bestfitness))

运行几次,标准偏差为0.058:

Cheryl         Kecia          Oren                    51.430
Tempie         Mark           Karl                    51.490
Fleta          Deloris        Jack                    51.540
Lynetta        Scott          Sanjuanita              51.533
Mack           Rosita         Sueann                  51.593
Shellie        Lyndia         Yong                    51.543
Jayson         Sophie         Tai                     51.480
Martin         Cassaundra     John                    51.410
Standard deviation of teams: 0.058

以下算法似乎运行良好。 它需要保留最快和最慢的人员,然后在中间找到人员,以使组平均值最接近全局平均值。 由于首先使用了极值,因此尽管选择池有限,但最后的平均值也不应该相差太远。

from bisect import bisect

times = sorted([47.20, 51.14, 49.95, 48.80, 46.60, 52.70, 57.65, 55.45, 52.30, 59.90, 49.24, 47.88, 51.11, 53.20, 48.90, 45.90, 54.43, 52.38, 49.90, 44.31, 58.12, 61.23, 49.38, 48.39])
average = lambda c: sum(c)/len(c)

groups = []
average_time = average(times)

while times:
    group = [times.pop(0), times.pop()]

    # target value for the third person for best average
    target = average_time * 3 - sum(group)
    index = min(bisect(times, target), len(times) - 1)

    # adjust if the left value is better than the right
    if index and abs(target - times[index-1]) < abs(target - times[index]):
        index -= 1

    group.append(times.pop(index))
    groups.append(group)

# [44.31, 61.23, 48.9]
# [45.9, 59.9, 48.8]
# [46.6, 58.12, 49.9]
# [47.2, 57.65, 49.38]
# [47.88, 55.45, 51.14]
# [48.39, 54.43, 51.11]
# [49.24, 53.2, 52.3]
# [49.95, 52.7, 52.38]

排序和迭代二进制搜索均为O(n log n),因此总复杂度为O(n log n)。 不幸的是,将其扩展到更大的群体可能很难。

最简单的方法可能是只创建3个存储桶-一个快速存储桶,一个中等存储桶和一个慢存储桶-并根据其合格时间向存储桶分配条目。

然后将速度最慢的速度,速度最快的速度和中值或中值组合在一起。 (不确定中位数或均值是我的首要选择。)重复直到您输入不足。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM