繁体   English   中英

Python:获取唯一的元组,以便所有元组中的元素不相交

[英]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_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中不可用)访问函数的默认参数。

例如,

not_seen.__defaults__[0].clear()

我认为没有可能避免for循环或列表理解。 您正在比较彼此之间的元素,因此必须以某种方式遍历它们。 如果由于内存使用情况而希望避免for循环或列表理解,那么您可能会对迭代器或生成器感兴趣,诸如irangexrange类的东西将为您节省(大量)内存。

(( 对此问题的理论解释是 :关于时间和空间的复杂性。此问题可能具有时间复杂度On )或更糟。但是对于空间(=内存)复杂度,您有一个很好的选择来避免On )并具有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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM