Python:从“元组列表”生成不考虑顺序的“元组集合”

10
如果我有以下元组列表:
[('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]

我希望去除重复的元组(在内容和项目顺序方面都是重复的),以便输出如下:

[('a', 'b'), ('c', 'd')]

或者

[('b', 'a'), ('c', 'd')]

我试着将其转换为集合再转换为列表,但输出结果仍会在生成的集合中保留('b', 'a')('a', 'b')两个元素!


将所有项目放入一个集合中,然后从集合条目创建一个列表 - 这可能已经被问过了。 - DisappointedByUnaccountableMod
你的问题在于 ('a', 'b') != ('b', 'a'),更具体地说,hash(('a', 'b')) != hash(('b', 'a'))。要做一个初始的列表推导式,对每个元组进行排序,然后转换为一个集合。 - R Nar
2
另外,您可能需要重新措辞您的问题,因为“在内容和项目顺序方面重复”意味着您不希望('a','b')('b','a')相等... - R Nar
5个回答

6
如果您不介意在set中使用frozenset:
l = [('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]

print(set(map(frozenset,l)))
{frozenset({'a', 'b'}), frozenset({'c', 'd'})}

如果需要,您可以将其转换回元组形式:
l = [('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]

print(list(map(tuple,set(map(frozenset ,l)))))
[('a', 'b'), ('d', 'c')]

或者使用一个集合并反转元组的顺序:
l = [('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]

seen, pairs = set(), []
for a,b in l:
    if (a,b) not in seen and (b,a) not in seen:
        pairs.append((a,b))
    seen.add((a,b))

6

尝试这个:

a = [('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]
b = list(set([ tuple(sorted(t)) for t in a ]))
[('a', 'b'), ('c', 'd')]

让我们来分解一下:

如果您对元组进行排序,它将变成一个已排序的列表。

>>> t = ('b', 'a')
>>> sorted(t)
['a', 'b']

对于列表 a 中的每个元组 t,进行排序并将其转换回元组。
>>> b = [ tuple(sorted(t)) for t in a ]
>>> b
[('a', 'b'), ('c', 'd'), ('a', 'b'), ('a', 'b')]

将生成的列表 b 转换为集合:这样值就变成唯一的了。然后将其转换回列表。

>>> list(set(b))
[('a', 'b'), ('c', 'd')]

完成了!

请注意,您可以使用生成器而非列表推导式,跳过中间列表b的创建。

>>> list(set(tuple(sorted(t)) for t in a))
[('a', 'b'), ('c', 'd')]

很好,除了你不需要实际创建中间列表 set([ tuple(sorted(t)) for t in a ])。可以使用 set(tuple(sorted(t)) for t in a) 代替。 - jscs
是的,这不是OP的要求。但他期望像 [('a', 'b'), ('c', 'd')] 这样的输出,所以我认为它必须是一个列表。 - Eric Citaire
最终形式必须是一个列表;中间的列表推导式是不必要的。 - jscs
好的,我明白你的意思。你是对的。我会保持我的答案不变,因为这样更容易理解。 :) - Eric Citaire
在这种情况下不起作用:[('b','a'), ('c','d'), ('b','a')]。输出应该是 [('b','a'), ('c','d')],但你的代码输出为 [('a','b'), ('c','d')]。在与其他元组进行比较时,元组内部的顺序不应该影响结果,但输出应包含原始元组,而不是修改后的元组。 - sam

4
如果顺序不重要,这可以解决您的问题。
a=[('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]


a=map(tuple,[sorted(i) for i in a])

print list(set(a))

输出:

[('a', 'b'), ('c', 'd')]

我喜欢这个解决方案,因为它不对元组的长度或原始内部顺序做出任何假设。 - jscs
@PadraicCunningham,实际上之前那个也可以。哈哈。我只是更喜欢你的那个,因为它使用了速记方式,但不幸的是我没有注意到它不会对列表元素进行排序,而是会对列表本身进行排序。 - Ahsanul Haque
2
是的,意思是 a = map(tuple, map(sorted, l)),如果是 Python2 的话,那么 imap 会更好。 - Padraic Cunningham

0

如果有人需要“先到先得”这种情况的用例,我想补充一个可能的第二个解决方案。

例如,假设我们将三个列表合并成元组列表:

# Make some lists (must be same size) 
a = [1,1,1,2,8,6,1]
b = [2,4,6,1,4,21,69]
c = [2,8,21,2,1,1,8]

# Lists to list of tuples
arr = []
for i in range(len(a)):
    new_row = (a[i],b[i],c[i])
    arr.append(new_row)

这样我们的原始数组看起来像:

(1, 2, 2)
(1, 4, 8)
(1, 6, 21)
(2, 1, 2)
(8, 4, 1)
(6, 21, 1)
(1, 69, 8)

在我们的情况下,我们想要删除类似于(2,1,2)和(8,4,1)这样的项目,因为它们等同于分别为(1,2,2)和(1,4,8)。
为了做到这一点,我们可以使用一个名为filtered的新空列表,并对原始数组中的每个元组使用itertools.permutations()
首先,我们检查每个项的任何排列是否存在于filtered列表中。
如果不存在,则添加。如果存在,则跳过重复项。

filtered = []
for i in range(len(arr)):
    it = itertools.permutations(arr[i])
    perms = []
    for p in it:
        perms.append(p)  
    check = any(item in perms for item in filtered)
    if not check: 
        filtered.append(arr[i])

现在,如果我们迭代 filtered 并打印,我们将看到我们截断的元组列表:
(1, 2, 2)
(1, 4, 8)
(1, 6, 21)
(1, 69, 8)

请注意,我们仅保留每个数字元组的第一个实例,并且不使用 set 进行操作可以确保在迭代过滤列表时具有相同的元素顺序。
唯一不确定的是以这种方式进行操作的时间/空间复杂度 - 如果有反馈,我很乐意听取。

-1

内置类型来拯救:

data = [('a', 'b'), ('c', 'd'), ('a', 'b'), ('b', 'a')]
set(map(frozenset, data))
{frozenset({'a', 'b'}), frozenset({'c', 'd'})}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接