【问题标题】:Sorting a list of tuples such that grouping by key is not desired对元组列表进行排序,这样就不需要按键分组
【发布时间】:2016-06-18 15:47:24
【问题描述】:

我有一个代表用户的 ID 列表,我正在编写一个函数来将该 ID 列表转换为用户匹配(配对)时间表,如下所示:

ids = [1, 2, 3, 4]

与此相关的时间表如下所示:

week 1: (1, 2), (3, 4)
week 2: (1, 3), (2, 4)
week 3: (1, 4), (2, 3)
week 4: (1, 2), (3, 4) [repeat of week 1]

等等。我试图根据用户 ID 的数量和由此产生的组合使用嵌套的 for 循环来做到这一点。

ids = [1,2,3,4]
matchups = []

#generate all the combinations of matchups
for subset in itertools.combinations(ids,2):
    matchups.append(subset)

这会将所有潜在的配对作为元组列表返回 - 太棒了!这就是我正在寻找的核心。我现在的问题是弄清楚如何将其转换为可用的东西。例如,上面的代码为matchups返回了这个列表:

[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

我尝试了一个复杂的递归函数来构建每周唯一配对的列表,然后我意识到如果我有上面的列表以正确的顺序,我可以简单地使用它来从头到尾分配配对,无论星期几,当我到达终点时重复。也就是说,我可以使用每周所需的已知配对数和已知的周数将配对分配给周。

为此,我一直试图弄清楚如何根据 相等的元素对元组列表进行排序。 sorted() 方法在这种情况下有很多有用的实现,但是我只能得到类似结果分组的结果,比如上面的。

我想找到一个 sorted() 的用法,它会返回以下内容,几乎就像一个反排序:

[(1, 2), (3, 4), (1, 3), (2, 4), (1, 4), (2, 3)]

有没有办法为此使用lambda

编辑:我刚刚意识到第一个元素需要与第六个配对,第二个与第五个配对,第三个与第四个配对。我不知道这是否适用于一般情况,但我希望它是可能的,因为我已经采取了其他措施来确保始终有偶数个 ID。

现在我确信有一些方法可以插入列表来实现这一点。

EDIT2:看起来之前的预感是不正确的——它不适用于 6 个 ID,而且很可能除此之外的任何东西也会失败。我回来试图弄清楚是否有一种方法可以分散而不是基于键进行排序

【问题讨论】:

  • random.shuffle 是反排序 :)
  • 非常正确-我意识到我的术语并不好-更像是我需要确保唯一性而不是按键分组。
  • 为什么不能使用 for 循环?第二个列表的顺序就是0 5 1 4 2 3
  • @pp_ 这如何概括超过 4 个元素
  • 当您说必须配对 1-6、2-5 和 3-4 时,我不清楚您的意思。这些对对您需要什么属性?例如,第 1-6 对使用 2 两次,而 4 则完全不使用。

标签: python sorting tuples


【解决方案1】:

我相信您正在寻找的是规范的round-robin 调度算法。

按您认为方便的任意顺序列出所有玩家,分两行:

1 2 3 4
5 6 7 8 

您的第一轮配对是 1-5、2-6、3-7、4-8。 连续几周,除了#1 位置之外的所有位置:上排左移,下排右移;从末端掉下来的那些(2 和 8)向上/向下移动到空位:

1 3 4 8
2 5 6 7 

如果您还需要平衡回家的配对,那么在您完成这次骑行后,每隔一周返回并交换行数。

【讨论】:

  • 感谢您的回答,但循环比我帖子中的组合有优势吗?他们甚至不同吗?我只有 4 个 ID,但我认为最终结果可能是相同的。无论如何,我对 combination() 方法的元组输出很满意,我想这更像是一个排序/列表操作问题。
  • 循环配对很容易推广到任意数量的 ID,并保证每个 ID 在每组 N/2 个配对中都有一个配对。根据您的描述,我认为这是您的 anti-sort 的重点。
  • 我想是的,但同时我相信combination() 方法的输出也给了我循环法的作用。有没有实现循环的包?
【解决方案2】:

可能是单一的解决方案,但它有效。也许有人可以让它更pythonic? :D

def antisort(x):
    if len(x) > 0:
        return [x[0]] + antisort(x[-1:0:-1])
    else:
        return []

>>> antisort([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)])
[(1, 2), (3, 4), (1, 3), (2, 4), (1, 4), (2, 3)]

这个切片符号只取第一个元素之后的所有内容并将其反转:

x[-1:0:-1]

【讨论】:

  • 感谢您的回答 - 我在编辑中的预感似乎不正确,因为这适用于 4 个元素,但不适用于 6 个
【解决方案3】:

这就是我在评论中的意思:

a = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
new = []

i, j = len(a)-1, 0

for n in range(i+1):
    if n % 2:
        new.append(a[i])
        i -= 1
    else:
        new.append(a[j])
        j += 1

print(new)

输出:

[(1, 2), (3, 4), (1, 3), (2, 4), (1, 4), (2, 3)]

【讨论】:

  • 这适用于 4 个元素,但不适用于更多元素 - 看来我编辑中的预感是不正确的。不过谢谢
  • @dkhaupt 为什么是 4?该列表有 6 个元素。还是你的意思是几周?
  • 是的 - 元组输出有 6 个元素,抱歉 - 我的意思是生成元组输出的 ID 列表。使用 4 个 ID,这可以工作,但是当我增加到 6 个 ID 时,它不再工作
【解决方案4】:

这是维基百科循环赛算法的实现。

x = range(1, 11) #10 players

def round_robin(someIds):
    someIds = someIds[::2] + someIds[1::2]
    first, someIds = [someIds[0]], someIds[1:]
    n = len(someIds)
    for i in range(n):
        top = someIds[:n/2]
        bottom = someIds[n/2:]
        yield zip(first+top, bottom)
        someIds = someIds[-1:] + someIds[:-1]


for thing in round_robin(x):
    print thing

输出

[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
[(1, 3), (4, 5), (6, 7), (8, 9), (10, 2)]
[(1, 4), (5, 6), (7, 8), (9, 10), (2, 3)]
[(1, 5), (6, 7), (8, 9), (10, 2), (3, 4)]
[(1, 6), (7, 8), (9, 10), (2, 3), (4, 5)]
[(1, 7), (8, 9), (10, 2), (3, 4), (5, 6)]
[(1, 8), (9, 10), (2, 3), (4, 5), (6, 7)]
[(1, 9), (10, 2), (3, 4), (5, 6), (7, 8)]
[(1, 10), (2, 3), (4, 5), (6, 7), (8, 9)]

【讨论】:

  • 这行得通,谢谢。你和@Prune 是对的——我应该从一开始就研究这个
  • 更改了它,使其按您要查找的顺序打印
  • 再次感谢 - 所以我使用 pythonfiddle.com 来解决这个问题,但是当我尝试在我的家庭环境(Python 3.4)中运行它时,我收到以下错误:TypeError: slice indices must be integers or None or have an __index__method。知道为什么会这样吗?
  • 我认为 python3 使用// 进行整数除法,而不是仅使用一个表示浮点运算的/
  • 非常感谢 - 我认为小提琴网站一定是在 2.x 上,但不知道 python3 有一个不同的整数除法运算符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
  • 1970-01-01
  • 2017-08-21
  • 2018-09-20
相关资源
最近更新 更多