【问题标题】:Python: How to get items that appear in only one set of a list of sets?Python:如何获取仅出现在一组列表中的项目?
【发布时间】:2019-09-24 05:06:20
【问题描述】:

我想创建一个函数,它接受一个或多个集合的列表,并找到列表中所有集合的对称差异,即结果应该是一组值,每个值只包含在一个的个人集。 (如果我错认为这是对称差异,请纠正我。)

例如:

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s3 = set([2, 3, 7])
>>> s4 = set([2, 5, 9])
>>> myfunc([s1, s2, s3, s4])
{1, 4, 5, 7, 9}

上面有没有内置的东西可以用来代替myfunc?还是我使用这样的东西:

def myfunc(sets: List[set]) -> set:

    sd = set()
    goners = set()
    for s in sets:
        still_ok = s - goners
        sd = sd.symmetric_difference(still_ok)
        goners = goners.union(s.difference(sd))
    return sd

有没有更好/更有效/“Pythonic”的方式来做到这一点?

【问题讨论】:

    标签: python set


    【解决方案1】:

    对于可以使用运算符和函数完成的内置 Python 对象的操作,运算符版本通常比函数版本更快,因为在访问实例属性和进行显式函数调用方面存在开销。此外,对集合执行就地更新可以避免创建额外的数据副本并使程序更高效。

    使用集合运算符的方法的改进版本如下所示:

    def myfunc_improved(sets: List[set]) -> set:
        sd = set()
        goners = set()
        for s in sets:
            sd ^= s - goners
            goners |= s - sd
        return sd
    

    性能测量:

    %timeit myfunc(sets)
    %timeit myfunc_improved(sets)
    
    3.19 µs ± 34.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    1.75 µs ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    

    【讨论】:

    • 不要从其他好的答案中拿走任何东西,但这个确实简洁优雅,对混合有一些很好的洞察力。赞一个!
    【解决方案2】:

    首先是的,您的观察是错误的,多个集合的对称差不是仅在单个集合中出现的元素集合,而是一组元素,其在所有集合中的总数为奇数,因此(s1,s2 的对称差,s3,s4) 将是 {1, 3, 4, 5, 7, 9}。

    def s_diff(li):
        res=set()
        for s in li:
            res =res.symmetric_difference(s)
        return res
    
    
    output:
    s_diff([s1,s2,s3,s4])
    {1, 3, 4, 5, 7, 9}
    

    【讨论】:

    • 3 不应出现在输出中
    • 如果您发现对称差异,那么您可以从公式中检查 3。
    • 3 存在于所有 s1 s2 s3 中。 OP 想要只存在于一组中的元素。
    • 但是这样就不是所有集合的对称差了。你真的想找到对称差异还是想找到所有集合中出现次数为 1 的元素。
    • 我正在寻找在所有集合中仅出现一次的元素(在任何两个集合之间不共享)。在多于两组的情况下,我对“对称差异”的含义感到困惑。
    【解决方案3】:

    您想要一个包含所有成员的集合 B,这些成员仅包含在 A 中的 一个 集合中。以下 (Python 3) 怎么样?

    from functools import reduce
    A = [set([1, 2, 3]), set([2, 3, 4]), set([2, 3, 7]), set([2, 5, 9])]
    B = set()
    for i in range(len(A)):
        U = reduce(set.union, A[:i]+A[(i+1):])
        B = B.union(set.difference(A[i], U))
    
    print(B)
    

    {1、4、5、7、9}

    【讨论】:

    • 3 不应出现在输出中。
    • 我现在修好了。确实忘记了 set.difference()
    【解决方案4】:

    这个呢:

    from collections import Counter
    
    s1 = set([1, 2, 3])
    s2 = set([2, 3, 4])
    s3 = set([2, 3, 7])
    s4 = set([2, 5, 9])
    print([k for k,v in Counter((*s1,*s2,*s3,*s4)).items() if v == 1])
    

    虽然这看起来不错,因为它是一个单线器,你必须记住它比你自己的方法慢一点:

    In [85]: def nicefunc(sets): 
        ...:     return [k for k,v in Counter(itertools.chain.from_iterable(sets)).items() if v == 1] 
        ...:                                                                                                                                                                                       
    
    In [86]: def nicefunc2(sets): 
        ...:     return [k for k,v in Counter( [i for s in sets for i in s]).items() if v == 1] 
        ...:                                                                                                                                                                                       
    
    In [87]: def nicefunc3(): 
        ...:     return [k for k,v in Counter((*s1,*s2,*s3,*s4)).items() if v == 1] 
        ...:                                                                                                                                                                                       
    
    In [88]: def myfunc(sets): 
        ...:     sd = set() 
        ...:     goners = set() 
        ...:     for s in sets: 
        ...:         still_ok = s - goners 
        ...:         sd = sd.symmetric_difference(still_ok) 
        ...:         goners = goners.union(s.difference(sd)) 
        ...:     return sd 
        ...:                                                                                                                                                                                       
    
    In [89]: sets = [s1, s2, s3, s4]                                                                                                                                                               
    
    In [90]: %timeit myfunc(sets)                                                                                                                                                                  
    2.25 µs ± 2.53 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    In [91]: %timeit nicefunc(sets)                                                                                                                                                                
    3.64 µs ± 23 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    In [92]: %timeit nicefunc2(sets)                                                                                                                                                               
    3.79 µs ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    In [94]: %timeit nicefunc3()                                                                                                                                                                   
    3.64 µs ± 18.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    

    您还可以选择另一种方法,它仍然是单线但速度更快:

    In [152]: def coolfunc(sets): 
         ...:     return set.union(*[sets[i]-set.union(*sets[:i],*sets[i+1:]) for i in range(len(sets))]) 
    
    In [153]: coolfunc(sets)                                                                                                                                                                       
    Out[153]: {1, 4, 5, 7, 9}
    
    In [154]: %timeit coolfunc(sets)                                                                                                                                                               
    3.34 µs ± 19.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    

    但是,正如@VBrail 所指出的,您对集合集合的对称集合差的定义是错误的。这是用于计算集合的实际对称集差异的单行方法,定义为

    集合的对称差只包含集合中奇数个集合中的元素wikipedia

    from functools import reduce                                                                                                                                                          
    s1 = set([1, 2, 3]) 
    s2 = set([2, 3, 4]) 
    s3 = set([2, 3, 7]) 
    s4 = set([2, 5, 9])                                                                                                                                                                   
    sets = [s1,s2,s3,s4]                                                                                                                                                                  
    reduce(set.symmetric_difference, sets)      
    

    {1、3、4、5、7、9}

    【讨论】:

      【解决方案5】:

      itertools 模块对这样的事情很有用:

      import itertools as it
      
      def only_exists_in_one_set(target):
          remover = []
          case = it.combinations(target, 2) #generate all combinations ignores order
          while True:
              try:
                  temp = next(case)
                  # AND all combos to find duplicates
                  remover.append(temp[0] & temp[1])
              except StopIteration:
                  break
          #flatten the nested list of sets passed to the function:
          target = [x for each_set in target for x in each_set]
          #flatten remover, eliminate duplicates with set
          for val in set([x for each_set in remover for x in each_set]):
              target = [a for a in target if a != val]    #remove all duplicate values
          return sorted(target)
      
      >>> only_exists_in_one_set([{1,2,3},{2,3,4},{2,3,7},{2,5,9}])
      
      >>> [1, 4, 5, 7, 9]
      

      不像许多方法那样简洁,但也许可读?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-13
        • 1970-01-01
        • 1970-01-01
        • 2011-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多