3

我是 Python 新手(在 2.7 中工作),我发现 SO 是一个非常有价值的资源!

假设我正在处理几个通常为 (ID, value) 形式的 2 元素元组列表,例如,

list1 = [(111, 222), (111, 333), (111, 444)]
list2 = [(555, 333), (555, 444), (555, 777)]
list3 = [(123, 444), (123, 888), (123, 999)]

我真正想做的是找到一种简单(且计算效率高)的方法来获取这些元组的第二个元素的交集。我查看了Python 文档,发现集合可能会做我想做的事……这篇文章有助于我理解如何获得两个列表的交集。

我知道我可以通过像这样循环遍历元组来制作三个全新的“仅值”列表:

newList1 = []
for tuple in list1:
   newList1.append(tuple[1])
newList2 = []
for tuple in list2:
   newList2.append(tuple[1])
newList3 = []
for tuple in list3:
   newList3.append(tuple[1])

然后像这样得到每一对的交集:

i_of_1and2 = set(newList1).intersection(newList2)
i_of_1and3 = set(newList2).intersection(newList3)
i_of_2and3 = set(newList1).intersection(newList3)

但是我的列表有点大——比如几十万(有时是几千万)的元组。这真的是获取这三个列表元组中第二个元素的交集的最佳方法吗?对我来说,这似乎……不优雅……。

谢谢你的帮助!

4

4 回答 4

3

你一开始就表现出一个大问题variable1通常是一个不好的迹象 - 如果你想有多个值,请使用数据结构,而不是大量带有编号名称的变量。这可以防止您一遍又一遍地重复您的代码,并有助于阻止错误。

让我们使用列表列表来代替:

values = [
    [(111, 222), (111, 333), (111, 444)],
    [(555, 333), (555, 444), (555, 777)],
    [(123, 444), (123, 888), (123, 999)]
]

现在我们只想获取子列表中每个元组的第二个元素。使用列表推导很容易计算:

>>> [[item[1] for item in sublist] for sublist in values]
[[222, 333, 444], [333, 444, 777], [444, 888, 999]]

然后,我们想要项目之间的交集,我们itertools.combinations()用来获得两个可能的各种对:

>>> for values, more_values in itertools.combinations(new_values, 2):
...     set(values).intersection(more_values)
... 
{444, 333}
{444}
{444}

所以,如果我们把它包装在一起:

import itertools

values = [
    [(111, 222), (111, 333), (111, 444)],
    [(555, 333), (555, 444), (555, 777)],
    [(123, 444), (123, 888), (123, 999)]
]

sets_of_first_items = ({item[1] for item in sublist} for sublist in values)
for values, more_values in itertools.combinations(sets_of_first_items, 2):
    print(values.intersection(more_values))

这给了我们:

{444, 333}
{444}
{444}

我在这里所做的更改是使内部列表成为集合推导,以避免创建列表只是为了将其转换为集合,并使用生成器表达式而不是列表推导,因为它是惰性求值的。

最后一点,如果您想要我们用来生成交集的列表的索引,使用内置enumerate()函数很简单:

sets_of_first_items = ({item[1] for item in sublist} for sublist in values)
for (first_number, first_values), (second_number, second_values) in itertools.combinations(enumerate(sets_of_first_items), 2):
    print("Intersection of {0} and {1}: {2}".format(first_number, second_number, first_values.intersection(second_values)))

这给了我们:

Intersection of 0 and 1: {444, 333}
Intersection of 0 and 2: {444}
Intersection of 1 and 2: {444}

编辑:

正如tonyl7126所指出的,这也是一个可以通过使用更好的数据结构得到很大帮助的问题。这里最好的选择是将用户 ID 的字典用于一组产品 ID。当您只需要一个集合并且稍后将其转换为集合时,没有理由将您的数据存储为列表,并且 dict 对于您尝试存储的数据类型是一个更好的解决方案。

请参见以下示例:

import itertools

values = {
    "111": {222, 333, 444},
    "555": {333, 444, 777},
    "123": {444, 888, 999}
}

for (first_user, first_values), (second_user, second_values) in itertools.combinations(values.items(), 2):
    print("Intersection of {0} and {1}: {2}".format(first_user, second_user, first_values.intersection(second_values)))

给我们:

Intersection of 555 and 123: {444}
Intersection of 555 and 111: {444, 333}
Intersection of 123 and 111: {444}
于 2012-05-14T01:38:09.550 回答
2

我不确定您是否已经阅读过有关 python 中的字典的信息,但这似乎适合您尝试结合列表做得更好。字典由键和值组成,就像您似乎用 2 元素元组模拟的一样。

例如,list1、list2 和 list3 可以表示为如下所示的字典(假设 111 是 id): your_dict = {"111": [222, 333, 444], "555": [333 , 444, 777], "123":[444, 888, 999]}

因此,如果您想获取特定 id 的所有值,例如“111”,您可以编写: your_dict.get("111") 并返回列表。这里也是一些关于字典的文档的链接。 http://docs.python.org/library/stdtypes.html#typesmapping

于 2012-05-14T01:45:30.150 回答
1

您可以利用该set.intersection(...)方法需要 2 组或更多组并找到它们的交集这一事实。此外,您可以使用列表推导来减少代码膨胀。最后,您可以使用参数列表解包使其成为单线。例如:

>>> list1 = [(111, 222), (111, 333), (111, 444)]
>>> list2 = [(555, 333), (555, 444), (555, 777)]
>>> list3 = [(123, 444), (123, 888), (123, 999)]
>>>
>>> set.intersection(*[set(t[1] for t in l) for l in (list1, list2, list3)])
set([444])

为了帮助您了解发生了什么,对的调用set.intersection(...)等效于以下 python 代码:

>>> allsets = []
>>> for l in (list1, list2, list3):
...   n = set()
...   for t in l:
...     n.add(t[1])
...   allsets.append(n)
... 
>>> allsets
[set([444, 333, 222]), set([777, 444, 333]), set([888, 444, 999])]
>>> allsets[0].intersection(allsets[1]).intersection(allsets[2])
set([444])
于 2012-05-14T01:36:20.080 回答
1

这是一个简单的方法。

>>> list1 = [(111, 222), (111, 333), (111, 444)]
>>> list2 = [(555, 333), (555, 444), (555, 777)]
>>> list3 = [(123, 444), (123, 888), (123, 999)]
>>> lists = [list1, list2, list3]
>>> set.intersection(*(set(zip(*list)[1]) for list in lists))
set([444])
  1. zip *技巧用于解压缩元组并获取第二个元素的集合 。
  2. set.intersection *用于将它们全部相交。

关于效率,我会先尝试简单的方法,看看是否足够快,然后再尝试优化。

于 2012-05-14T04:38:43.333 回答