有一个元组列表:
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解决方案也没有问题。
提前感谢您的意见。
答案 0 :(得分:2)
这是在普通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)。< / p>
例如,
not_seen.__defaults__[0].clear()
答案 1 :(得分:0)
我认为不可能避免for循环或列表理解。你正在比较彼此的元素,所以你必须循环它们。如果由于内存使用而想要避免for循环或列表推导,那么你可能会对迭代器或生成器感兴趣,像irange
或xrange
这样可以节省你(大量的)内存。
((这个问题的理论解释:它是关于时间和空间的复杂性。这个问题很可能有时间复杂度 O ( n < / em>)或更糟。但是对于空间(=内存)复杂性,你有一个很好的改变,以避免 O ( n ),并有类似 O (1)或 O (log n )。))
不知道你的故事的熊猫方面。我对那个包没有足够的经验。
答案 2 :(得分:0)
我不知道你为什么要避免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那么多给你一个答案。