嵌套列表的唯一性

93

我想知道一种高效的方法来确保数据对象唯一:

testdata =[ ['9034968', 'ETH'], ['14160113', 'ETH'], ['9034968', 'ETH'], ['11111', 'NOT'], ['9555269', 'NOT'], ['15724032', 'ETH'], ['15481740', 'ETH'], ['15481757', 'ETH'], ['15481724', 'ETH'], ['10307528', 'ETH'], ['15481757', 'ETH'], ['15481724', 'ETH'], ['15481740', 'ETH'], ['15379365', 'ETH'], ['11111', 'NOT'], ['9555269', 'NOT'], ['15379365', 'ETH']
]

每个数据对中,左侧的数字字符串和右侧的类型描述数据元素的唯一性。返回值应该是一个列表嵌套列表,与测试数据相同,但仅保留唯一值。


2
你10年前就在处理以太币了?哇!那时候你是怎么知道它们的呢! - rsc05
7个回答

166

你可以使用一个集合:

unique_data = [list(x) for x in set(tuple(x) for x in testdata)]

你还可以查看这个页面,该页面对许多保留或不保留顺序的方法进行了基准测试。


请注意,使用此方法会丢失排序。如果排序很重要,则需要在之后进行排序或手动删除项目。 - Wolph
1
我遇到了一个错误:TypeError: unhashable type: 'list'。Python 2.6.2,Ubuntu Jaunty。 - Manoj Govindan
@Hellnar:他刚刚更新了代码,使用元组,现在你不会再遇到那个问题了 :) - Wolph
1
@Manoj Govindan:问题出在列表不可哈希,只有可哈希类型才能用于集合。我通过转换为元组,然后再转换回列表来解决了这个问题。不过,也许原始问题的提出者应该使用一个元组列表。 - Mark Byers
1
@Khan:Python的集合是无序的。这并不意味着你不能从list(some_set)得到一致的结果,但它意味着你不能以任何方式设置或影响排序顺序。更多信息请参见:https://dev59.com/X2ct5IYBdhLWcg3wV8Ck - Wolph
@Wolph:将set替换为dict.fromkeys,保持其他内容不变,在CPython/PyPy 3.6+(或任何Python 3.7+)上,您将保留顺序(每个重复值的第一个副本保留在原始顺序中,后续重复项被丢弃)。 - ShadowRanger

11

我尝试了 @Mark 的答案,但是出现了错误。将列表及其所有元素转换为元组后使其运行正常。不确定这是否是最佳方式。

list(map(list, set(map(lambda i: tuple(i), testdata))))

当然,同样的事情也可以使用列表推导式来表达。

[list(i) for i in set(tuple(i) for i in testdata)]

我正在使用Python 2.6.2。

更新

@Mark已经改变了他的答案。他目前的答案使用元组并且可以工作。我的也是 :)

更新2

感谢@Mark。我已经将我的答案更改为返回一个列表的列表而不是元组的列表。


1
这里有一个小技巧:你可以直接写 foo,而不是 lambda x: foo(x) - Mark Byers
@Mark:当foo是可调用的时候。明白了。 - Manoj Govindan

5

使用numpy中的 unique 函数来解决这个问题:

import numpy as np

np.unique(np.array(testdata), axis=0)

请注意,必须指定 axis 关键字,否则列表将首先被展平。
或者,使用vstack
np.vstack({tuple(row) for row in testdata})

1
这个选项非常棒,因为它不限制你只能使用元组。你可以找到多个属性的唯一列表。 - wunderkind

3

稍微深入一点解释@Mark Byers的解决方案,你也可以仅使用一个列表推导式并转换数据类型以获取你需要的结果:

testdata = list(set(tuple(x) for x in testdata))

另外,如果你不喜欢列表推导式,因为很多人认为它们难以理解,你可以使用for循环来完成相同的操作:

for i, e in enumerate(testdata):
    testdata[i] = tuple(e)
testdata = list(set(testdata))

2
import sets
testdata =[ ['9034968', 'ETH'], ['14160113', 'ETH'], ['9034968', 'ETH'], ['11111', 'NOT'], ['9555269', 'NOT'], ['15724032', 'ETH'], ['15481740', 'ETH'], ['15481757', 'ETH'], ['15481724', 'ETH'], ['10307528', 'ETH'], ['15481757', 'ETH'], ['15481724', 'ETH'], ['15481740', 'ETH'], ['15379365', 'ETH'], ['11111', 'NOT'], ['9555269', 'NOT'], ['15379365', 'ETH']]
conacatData = [x[0] + x[1] for x in testdata]
print conacatData
uniqueSet = sets.Set(conacatData)
uniqueList = [ [t[0:-3], t[-3:]] for t in uniqueSet]
print uniqueList

2
同时,sets模块已被弃用,请使用内置的set类型。 - Björn Pollex
简单的想法有效。 - nish

1

我本来想发布自己对此的看法,直到我注意到@pyfunc已经提出了类似的观点。无论如何,我还是会发布我的问题解决方案,以防有帮助。

testdata =[ ['9034968', 'ETH'], ['14160113', 'ETH'], ['9034968', 'ETH'], ['11111', 'NOT'], ['9555269', 'NOT'], ['15724032', 'ETH'], ['15481740', 'ETH'], ['15481757', 'ETH'], ['15481724', 'ETH'], ['10307528', 'ETH'], ['15481757', 'ETH'], ['15481724', 'ETH'], ['15481740', 'ETH'], ['15379365', 'ETH'], ['11111', 'NOT'], ['9555269', 'NOT'], ['15379365', 'ETH']
]
flatdata = [p[0] + "%" + p[1] for p in testdata]
flatdata = list(set(flatdata))
testdata = [p.split("%") for p in flatdata]
print(testdata)

基本上,您可以使用列表推导将列表中的每个元素连接成一个字符串,以便您获得一组单个字符串。这样更容易将其转换为集合,使其成为唯一的。然后,您只需在另一端拆分它并将其转换回原始列表即可。
我不知道这在性能方面如何比较,但我认为这是一个简单易懂的解决方案。

1
如果你有一个对象列表,那么你可以修改@Mark Byers的答案为:
unique_data = [list(x) for x in set(tuple(x.testList) for x in testdata)]

其中testdata是一个对象列表,其中每个对象都有一个名为testList的属性列表。


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