[英]Python: Get unique tuples such that elements across all tuples are disjoint
有一个元组列表:
all_tups = [(1, 2), (2, 1), (3, 4), (3, 5), (4, 3), (6, 8), (6, 7), (7, 9)]
我想做什么:获取元组,使得如果在任何一个元组中都出现了一个元素,那么具有该元素的任何其他元组都应该被丢弃(无论该元素在任何元组中的位置如何)。
因此,可能有几个所需的输出:
[(1,2) (3,4) (6,8) (7,9)] OR
[(2,1) (4,3) (6,8) (7,9)]
等等。
最初,每个元组的第一个元素来自Pandas数据帧的一列,每个元组的第二个元素来自同一数据帧的另一列。
C1 C2 0 1 2 1 2 1 2 3 4 3 3 5 4 4 3 5 6 8 6 6 7 7 7 9
在实际问题中,数据框中有数百万行。 因此,我不是在寻找基于for循环的解决方案。 除了基于for循环的解决方案外,任何适用于数据框或数百万元组的方法都可以。
到目前为止,我已经尝试过:我已经能够使用冻结集获取唯一元组的列表:
uniq_tups = {frozenset(k) for k in all_tups}
(当然,我也希望避免使用列表推导)。 这给了我:
{frozenset({1, 2}),
frozenset({6, 7}),
frozenset({3, 5}),
frozenset({3, 4}),
frozenset({6, 8}),
frozenset({7, 9})}
我似乎无法通过这种解决方案取得进展的非for循环方式,也无法使用任何其他避免循环的方法。
我目前正在使用Python 3.5,但是使用Python 2.7解决方案也没有问题。
预先感谢您的输入。
这是在普通Python中执行此操作的合理有效的方法。 我们使用一个not_seen
函数来测试一个元组是否包含已在可接受的元组中看到的元素。
all_tups = [(1, 2), (2, 1), (3, 4), (3, 5), (4, 3), (6, 8), (6, 7), (7, 9)]
def not_seen(t, seen=set()):
if t[0] in seen or t[1] in seen:
return False
seen.update(t)
return True
unique = list(filter(not_seen, all_tups))
print(unique)
产量
[(1, 2), (3, 4), (6, 8), (7, 9)]
not_seen
有一个小问题:它使用seen
的默认可变参数来缓存看到的元素,并且该集合无法清除,因此,如果您需要再次执行此操作,则seen
仍然会保留旧元素。 我们可以 seen
一个全局的,但是运行速度会变慢。 另一种选择是使用工厂函数来生成每次我们需要时都seen
的原始版本。 例如:
def make_checker():
def not_seen(t, seen=set()):
if t[0] in seen or t[1] in seen:
return False
seen.update(t)
return True
return not_seen
not_seen = make_checker()
FWIW,这是not_seen
的紧凑版本; 它应该几乎和原始版本一样高效,如果它实际上更快,我会感到惊讶。 :)
def not_seen(t, seen=set()):
return False if t[0] in seen or t[1] in seen else seen.update(t) or True
我们可以将该压缩版本转换为lambda,然后我们不必担心清除seen
集的问题。
all_tups = [(1, 2), (2, 1), (3, 4), (3, 5), (4, 3), (6, 8), (6, 7), (7, 9)]
unique = list(filter(lambda t, seen=set():
False if t[0] in seen or t[1] in seen else seen.update(t) or True, all_tups))
print(unique)
这是一个Numpy实现。 首先,我们将数据转换为2D Numpy数组。 然后,将not_seen
与numpy.apply_along_axis
一起使用以创建一个Numpy布尔数组,该数组表示应接受的对,然后使用该布尔数组选择所需的对。
import numpy as np
def not_seen(t, seen=set()):
if t[0] in seen or t[1] in seen:
return False
seen.update(t)
return True
all_tups = [(1, 2), (2, 1), (3, 4), (3, 5), (4, 3), (6, 8), (6, 7), (7, 9)]
all_tups = np.array(all_tups)
print(all_tups, all_tups.dtype)
print('- ' * 20)
filtered = all_tups[np.apply_along_axis(not_seen, 1, all_tups)]
print(filtered)
产量
[[1 2]
[2 1]
[3 4]
[3 5]
[4 3]
[6 8]
[6 7]
[7 9]] int32
- - - - - - - - - - - - - - - - - - - -
[[1 2]
[3 4]
[6 8]
[7 9]]
这应该比上面的普通Python实现更快。 循环过程本身应该更快,瓶颈是我们仍在调用not_seen
,这是一个普通的Python函数。 另外,它使用更多的RAM,因为它必须构造布尔数组。
更新
它实际上是可以清除seen
从外部设定not_seen
功能。 我们可以通过函数的.__default__
属性(或旧版本的Python 2中的.func_defaults
; .__default__
在Python 2.6中有效,但在2.5中不可用)访问函数的默认参数。
例如,
not_seen.__defaults__[0].clear()
我认为没有可能避免for循环或列表理解。 您正在比较彼此之间的元素,因此必须以某种方式遍历它们。 如果由于内存使用情况而希望避免for循环或列表理解,那么您可能会对迭代器或生成器感兴趣,诸如irange
或xrange
类的东西将为您节省(大量)内存。
(( 对此问题的理论解释是 :关于时间和空间的复杂性。此问题可能具有时间复杂度O ( n )或更糟。但是对于空间(=内存)复杂度,您有一个很好的选择来避免O ( n )并具有O (1)或O (log n )的值。))
不了解您故事的熊猫方面。 我对该程序包没有足够的经验。
我不知道为什么要避免for循环,而同时在示例解决方案中使用它们,但是一个简单的(我认为)函数可以产生输出:
all_tups = [(1, 2), (2, 1), (3, 4), (3, 5), (4, 3), (6, 8), (6, 7), (7, 9)]
def tup_gen(tuple_list):
s = set()
for element in tuple_list:
if s.intersection(element):
continue
yield element
s.update(element)
这样做很懒,一次只生成一个元组(这使您可以处理大数据集),并且输出将是:
result = list(tup_gen(all_tups))
print(result)
[(1, 2), (3, 4), (6, 8), (7, 9)]
当然,这是不使用任何特定库的方式。 我非常确定必须有一些更快的方法(甚至直接在PF中使用),但是我对PF的了解还不够多,尚无法给您答案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.