Python:获取独特的元组,使所有元组中的元素不相交

时间:2016-11-10 10:14:53

标签: python list pandas set tuples

有一个元组列表:

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解决方案也没有问题。

提前感谢您的意见。

3 个答案:

答案 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_seennumpy.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循环或列表推导,那么你可能会对迭代器或生成器感兴趣,像irangexrange这样可以节省你(大量的)内存。

((这个问题的理论解释:它是关于时间和空间的复杂性。这个问题很可能有时间复杂度 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那么多给你一个答案。