【问题标题】:Most efficient way to encode presence of elements of a list in another list编码另一个列表中存在的列表元素的最有效方法
【发布时间】:2019-09-16 06:48:28
【问题描述】:

假设我有一个像这样的 排序 主列表: main = ["a", "b", "cd", "e"]

还有很多其他列表,例如 l1 = ["a", "cd"]l2 = ["b", "cd"]。还保证了单个列表的所有元素,如l1l2 也属于main并非所有单个列表的长度都必须相同,但所有列表的长度都小于或等于main

现在,对于每个单独的列表,我想创建一个向量来编码 main 的哪些元素出现在单独的列表中。例如对于l1,我们将得到一个向量(numpy 数组)[1,0,1,0],因为main 的第一个和第三个元素(我可以说“第一个”和“第三个”,因为main 已排序)。同样对于l2,我们将得到[0,1,1,0]

什么是执行此操作的有效方法?我不想要天真的蛮力方法,因为那会非常缓慢。是否涉及任何中间 numpy 转换/操作?很高兴得到这方面的帮助!

【问题讨论】:

  • 可以有["a", "a", "a"]吗?它的预期输出是什么?
  • 您通常会有多少这样的个人列表?
  • @Divakar:大约 600000。主列表有大约 4000 个元素。
  • 发布的解决方案是否适合您?
  • @Divakar:我使用过 numpy.isin() 因为我必须将该功能包装在另一个函数中,并且 np.isin 很容易实现(内置单行)并且看起来效率也很高

标签: python arrays performance numpy


【解决方案1】:

你可以只使用列表推导:

main = ["a", "b", "cd", "e"]
l1 = ["a", "cd"]

print ([item in l1 for item in main])

输出:

[True, False, True, False]

print ([1 if item in l1 else 0 for item in main])

输出:

[1, 0, 1, 0]

【讨论】:

    【解决方案2】:

    这是一个np.searchsorted -

    def isin_many(main, L):
        # main is the array or list where presence is to be detected
        # L is list of lists whose presence is to be detected
        main_ar = np.asarray(main)
    
        La = np.concatenate(L)
        sidx = np.argsort(main_ar)
        idx = sidx[np.searchsorted(main_ar, La, sorter=sidx)]
    
        n = len(L)
        out = np.zeros((n,len(main_ar)), dtype=bool)
        row = np.repeat(np.arange(n),np.array([len(l) for l in L]))
        out[row,idx] = True
        return out.view('i1')
    

    输出将是一个2D 数组,其中的每一行都会为我们提供主列表中每个单独列表的存在。考虑到此类列表的数量很大,它主要是一种矢量化方法(只有循环是获取单个列表的长度,这在计算上可以忽略不计)。

    示例运行 -

    In [44]: main = ["a", "b", "cd", "e", "F"]
        ...: l1 = ["a", "cd"]; l2 = ["b", "cd"]; l3 = ['F']
    
    In [45]: isin_many(main, [l1,l2,l3])
    Out[45]: 
    array([[1, 0, 1, 0, 0], # presence of l1
           [0, 1, 1, 0, 0], # presence of l2 and so on ...
           [0, 0, 0, 0, 1]], dtype=int8)
    

    【讨论】:

    • 非常感谢!还有一件事 - 如果我在主列表和单个列表中都有 NaN 值怎么办?我已经读过,始终牢记 NaN 值是一种很好的做法,因此我想忽略(分配 0 到)与单个列表中的 NaN 值对应的索引。这可能吗?
    • @ShirishKulhari 那么,是 numpy.nan 还是 'nan'(string) 还是 None?
    【解决方案3】:

    正如有人指出的那样,您可以使用np.isin

    def isin_many_np(main, L):
        return np.array([np.isin(main, l) for l in L])
    

    但是,如果您要多次这样做,使用字典为 main 的每个元素提供其位置会更有效。

    那么你只需要写以下内容:

    def isin_dict(main_d, L):
        r = np.zeros(len(main_d), dtype=bool)
        for li in L:
          r[main_d[li]] = 1
        return r
    
    def isin_many_dict(main, L):
        main_d = {s:i for i,s in enumerate(main)}
        return np.array([isin_dict(main_d, l) for l in L]).view('i1')
    

    这里有一些针对np.isin 和@Divakar 答案的基准:

    import random
    import string
    
    main = sorted([''.join(random.sample(string.ascii_letters, 5)) for i in range(4000)])
    L = [random.sample(main, 2000) for i in range(6000)]
    
    %timeit t = isin_many_np(main, L)                                                                                                                
    6.67 s ± 121 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit r = isin_many_divakar(main, L)                                                                                                                      
    2.17 s ± 50.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit s = isin_many_dict(main, L)                                                                                                               
    1.27 s ± 11.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    【讨论】:

      【解决方案4】:

      您可以尝试使用 numpy 数组在列表推导中使用 if else。

      import numpy as np
      
      #encode presence of elements of a list in another list
      def list_contains(mainlist, otherlist):
          result = np.array([1 if x in otherlist else 0 for x in mainlist])
          return result
      

      你可以检查

      main = ["a", "b", "cd", "e"]
      l1 = ["a", "cd"]
      l2 = ["b", "cd"]
      
      #check 1
      out1 = list_contains(main, l1)
      print(out1)  # [1 0 1 0]
      type(out1)  # numpy.ndarray
      
      #check 2
      out2 = list_contains(main, l2)
      print(out2)  # [0 1 1 0]
      type(out2)  # numpy.ndarray
      

      【讨论】:

        【解决方案5】:

        我的解决方案使用 numpy isin(它给出 True/False),效率方面,因为在许多情况下,它取决于大小,在您的小示例中列表理解更快,但在较长的示例中慢得多,这里是:

        import itertools
        import string
        import random 
        import numpy as np
        
        main = ["a", "b", "cd", "e"]
        l1 = ["a", "cd"]
        
        def f1(main, l):
            return [1 if x in l else 0 for x in main]
        
        %%timeit
        f1(main, l1)
        output: 649 ns ± 15.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
        
        %%timeit
        np.isin(main, l1)
        output:23 µs ± 122 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
        

        还有一个很长的例子:

        letters_and_couples = \
        [''.join(x) for x in itertools.combinations(string.ascii_lowercase, 2)]+\
        [''.join(x) for x in itertools.combinations(string.ascii_lowercase, 1)]
        
        long_main = random.choices(letters_and_couples,k=1000000)
        long_l1 = random.choices(letters_and_couples,k=1000)
        
        %%timeit
        f1(long_main, long_l1)
        output: 5.02 s ± 71.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        %%timeit
        np.isin(long_main, long_l1)
        output: 241 ms ± 1.89 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        

        【讨论】:

          猜你喜欢
          • 2017-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-11
          • 2011-11-28
          • 2019-06-18
          相关资源
          最近更新 更多