【问题标题】:In python, how to combine two very large lists with associated counts lists into single list with associated counts list?在python中,如何将两个非常大的列表与关联的计数列表组合成具有关联计数列表的单个列表?
【发布时间】:2021-10-11 13:35:36
【问题描述】:

首先,请注意我一般使用“列表”一词。我怀疑最好的解决方案将使用某种集合,例如有序字典、“有序集”等。

我使用两个列表来表示一组对象——一个对象列表和一个计数列表。对象列表包含对象,每个对象都是唯一的。对象由长整数表示。第二个列表与第一个对齐,项目是整数,表示对象列表中的每个关联对象在集合中的数量(计数,如直方图)。

唯一对象的可能数量很大,例如 2**64。但是在实践中,一个集合中可能会有 20,000-30,000 个唯一对象。

现在,给定两个集合:集合 A(由列表 obj_Acnt_A 表示)和集合 B(由列表 obj_Bcnt_B 表示),我需要将它们组合/添加到一个新集合中, C. 因此,我需要找到 A 中也在 B 中的所有对象,并将这些对象的 A 和 B 计数相加。仅在 A 中或仅在 B 中的对象将保留其各自集合中的计数。

例如,在下面的列表中,对象 750 在两个列表中,因此组合集合 C 中 750 的计数是 A 和 B 集合中的计数之和。

ojb_A = [4903, 750, 29868, 833]
cnt_A = [1,    3,   24,    3  ]

ojb_B = [2357, 39,  750,   38 ]
cnt_B = [8,    52,  6,     2  ]

将 A 和 B 组合成 C 得到:

ojb_C = [4903, 750, 29868, 833, 2357, 39,  38]
cnt_C = [1,    9,   24,    3,   8,    52,  2 ]

正如最初提到的,我怀疑为了提高效率需要对象列表的一些有序表示,尽管我没有按照上面示例中的顺序显示项目。

[编辑]: 我刚刚发现collections.Counter 可能满足我的需求。但同样,我的收藏中包含大量独特的对象,因此我正在寻找一种高效/快速的解决方案。

【问题讨论】:

  • 你还需要维护元素的顺序吗?
  • 我不需要维护订单或列表中的项目/元素。我只是怀疑订购的物品对于有效的解决方案至关重要。
  • 是的,将它们作为原始对象之一排序需要更多的运行时间,但使用 pandas 仍然可以有效地实现
  • 仅供参考,我认为任何初始排序/排序时间都可以忽略,假设有序输入导致有序输出。我将调用此操作数千次,因此与组合集合所花费的时间相比,初始排序所花费的时间应该可以忽略不计。
  • @eroot163pi 我很欣赏对速度的深入检查!我应该澄清/重申我所说的 2^64 个唯一对象的意思是单个对象可以是 0 到 2^64-1 之间的整数。但是单个集合中可能“只有” 1e4 或 1e5 个独特的对象。所以目前你探索的“猛犸象”尺寸超出了我的需要(可能会改变)。尽管如此,使用 numba 可能还是有用的,因为我必须重复操作数千次,并且任何加速,无论多么小,都会被放大。谢谢!

标签: python list sorting collections set


【解决方案1】:

您可以使用defaultdict 来计算所有计数:

from collections import defaultdict

ojb_A = [4903, 750, 29868, 833]
cnt_A = [1,    3,   24,    3  ]

ojb_B = [2357, 39,  750,   38 ]
cnt_B = [8,    52,  6,     2  ]

def count(out, ojb, cnt):
    for index,obj in enumerate(ojb):
        out[obj] += cnt[index]
    
def split_out(out):
    return list(out.keys()), list(out.values())

out = defaultdict(int)
count(out, ojb_A, cnt_A)
count(out, ojb_B, cnt_B)

ojb_C, cnt_C = split_out(out)
print(ojb_C, cnt_C)

【讨论】:

    【解决方案2】:

    collections.Counterzip 的组合可能是最简洁的方式,也应该相当有效。

    from collections import Counter
    
    counts = Counter(dict(zip(ojb_A, cnt_A)))
    counts.update(dict(zip(ojb_B, cnt_B)))
    
    ojb_C, cnt_C = zip(*counts.items())
    

    (仅供参考,ojb_Ccnt_C 是元组。)

    【讨论】:

      【解决方案3】:

      您可以使用pandas 有效地做到这一点:

      import pandas as pd
      df1 = pd.DataFrame(zip(ojb_A,cnt_A))
      df2 = pd.DataFrame(zip(ojb_B,cnt_B))
      
      df_combined = pd.concat([df1,df2]).groupby(0).sum()
      ojb_C = list(df_combined.index.values)
      cnt_C = list(df_combined[1])
      

      只需 303 ms ± 24.9 ms 即可组合 2 个长度为 ~100000 的对象

      【讨论】:

        【解决方案4】:

        最新更新

        我调整了下面的代码以使用 numba,对于更大的输入,它的速度提高了 2 倍,并且可以处理更大的输入而不会在我的本地被杀死。 这会将用 numba.njit 修饰的函数转换为机器码,并在输入量很大时发光。由于编译,后续运行总是比第一次运行快。我知道这对于 10k-20k 可能不是必需的,但对于 2**64 可能需要。 (P.S. 我在工作中使用 numba 来提高速度refer here to see how it can speed up

        使用 numba

        import numpy as np
        import numba as nb
        ojb_A = np.array(ojb_A)
        ojb_B = np.array(ojb_B)
        cnt_A = np.array(cnt_A)
        cnt_B = np.array(cnt_B)
        @nb.njit
        def count(out, ojb, cnt, i):
            for index in nb.prange(ojb.shape[0]):
                out[ojb[i]] = out.get(ojb[i], 0) + cnt[index]
            
        def split_out(out):
            return list(out.keys()), list(out.values())
        
        out = nb.typed.Dict.empty(
            key_type=nb.core.types.int64,
            value_type=nb.core.types.int64,
        )
        count(out, ojb_A, cnt_A, 0)
        count(out, ojb_B, cnt_B, 1)
        
        ojb_C, cnt_C = split_out(out)
        

        new_test.py

        import random
        import sys
        import time
        import numba as nb
        
        random.seed(31212223)
        
        MAX = 2 ** int(sys.argv[2])
        
        def f3():
            ojb_A = random.sample(range(1, 2**30), MAX)
            cnt_A = random.sample(range(1, 2**30), MAX)
            ojb_B = random.sample(range(1, 2**30), MAX)
            cnt_B = random.sample(range(1, 2**30), MAX)
            s1 = time.time()
            from collections import defaultdict
            # @nb.jit
            def count(out, ojb, cnt):
                for index,obj in enumerate(ojb):
                    out[obj] += cnt[index]
                
            def split_out(out):
                return list(out.keys()), list(out.values())
        
            out = defaultdict(int)
            count(out, ojb_A, cnt_A)
            count(out, ojb_B, cnt_B)
        
            ojb_C, cnt_C = split_out(out)
            # print(ojb_C, cnt_C)
            s2 = time.time()
            print('quamrana', s2 - s1)
        
        def f3_1():
            ojb_A = random.sample(range(1, 2**30), MAX)
            cnt_A = random.sample(range(1, 2**30), MAX)
            ojb_B = random.sample(range(1, 2**30), MAX)
            cnt_B = random.sample(range(1, 2**30), MAX)
            s1 = time.time()
            import numpy as np
            import numba as nb
            ojb_A = np.array(ojb_A)
            ojb_B = np.array(ojb_B)
            cnt_A = np.array(cnt_A)
            cnt_B = np.array(cnt_B)
            @nb.njit
            def count(out, ojb, cnt, i):
                for index in nb.prange(ojb.shape[0]):
                    out[ojb[i]] = out.get(ojb[i], 0) + cnt[index]
                
            def split_out(out):
                return list(out.keys()), list(out.values())
        
            out = nb.typed.Dict.empty(
                key_type=nb.core.types.int64,
                value_type=nb.core.types.int64,
            )
            count(out, ojb_A, cnt_A, 0)
            count(out, ojb_B, cnt_B, 1)
        
            ojb_C, cnt_C = split_out(out)
            # print(ojb_C, cnt_C)
            s2 = time.time()
            print('eroot163pi', s2 - s1)
        
        
        if __name__ == '__main__':
            # Two runs to show subsequent run is faster
            eval(f'{sys.argv[1]}()')
            eval(f'{sys.argv[1]}()')
        

        在 2^22-27 的庞大案例上运行

        • Numba 进程成功完成 2^26,但正常的 python 被杀死
        • Numba 在这些输入方面始终更快,并且能够处理受 2^27 限制的更大输入
        (base) xxx@xxx:~$ python test.py f3 22
        quamrana 3.2768890857696533
        quamrana 3.2760112285614014
        (base) xxx@xxx:~$ python test.py f3_1 22
        eroot163pi 2.4150922298431396
        eroot163pi 1.8658664226531982
        (base) xxx@xxx:~$ python test.py f3 23
        quamrana 6.903605937957764
        quamrana 7.187666654586792
        (base) xxx@xxx:~$ python test.py f3_1 23
        eroot163pi 4.326314926147461
        eroot163pi 3.6970062255859375
        (base) xxx@xxx:~$ python test.py f3 24
        quamrana 14.135217905044556
        quamrana 14.102455615997314
        (base) xxx@xxx:~$ python test.py f3_1 24
        eroot163pi 8.097218751907349
        eroot163pi 7.514840602874756
        (base) xxx@xxx:~$ python test.py f3 25
        quamrana 29.825793743133545
        quamrana 30.300193786621094
        (base) xxx@xxx:~$ python test.py f3_1 25
        eroot163pi 16.243808031082153
        eroot163pi 15.114825010299683
        (base) xxx@xxx:~$ python test.py f3 26
        Killed
        (base) xxx@xxx:~$ python test.py f3_1 26
        eroot163pi 35.73880386352539
        eroot163pi 34.74332834847338
        (base) xxx@xxx:~$ python test.py f3_1 27
        Killed
        



        其他方法的表现

        这是上述一些方法在庞大的测试用例上的比较。因为我自己对性能很好奇,所以 quamrana 的表现非常好。

        对于大于 2**26 的数组长度,上述所有解决方案尝试均被终止。 (由于我自己的系统限制,16gb,12core)

        对于其余部分,quamrana 的解决方案始终表现最佳,Kapil 的解决方案排名第二。每个解决方案都提供几乎 2-3 倍于下一个解决方案始终

        test.py

        import random
        import sys
        import time
        
        random.seed(31212223)
        
        MAX = 2 ** int(sys.argv[2])
        ojb_A = random.sample(range(1, 2**30), MAX)
        cnt_A = random.sample(range(1, 2**30), MAX)
        ojb_B = random.sample(range(1, 2**30), MAX)
        cnt_B = random.sample(range(1, 2**30), MAX)
        
        def f1():
            s1 = time.time()
            import pandas as pd
            df1 = pd.DataFrame(zip(ojb_A,cnt_A))
            df2 = pd.DataFrame(zip(ojb_B,cnt_B))
        
            df_combined = pd.concat([df1,df2]).groupby(0).sum()
            ojb_C = list(df_combined.index.values)
            cnt_C = list(df_combined[1])
            s2 = time.time()
            print('Kapil', s2 - s1)
        
        def f2():
            s1 = time.time()
            from collections import Counter
        
            counts = Counter(dict(zip(ojb_A, cnt_A)))
            counts.update(dict(zip(ojb_B, cnt_B)))
        
            ojb_C, cnt_C = zip(*counts.items())
            s2 = time.time()
            print('fsimonjetz', s2 - s1)
        
        def f3():
            s1 = time.time()
            from collections import defaultdict
        
            def count(out, ojb, cnt):
                for index,obj in enumerate(ojb):
                    out[obj] += cnt[index]
                
            def split_out(out):
                return list(out.keys()), list(out.values())
        
            out = defaultdict(int)
            count(out, ojb_A, cnt_A)
            count(out, ojb_B, cnt_B)
        
            ojb_C, cnt_C = split_out(out)
            # print(ojb_C, cnt_C)
            s2 = time.time()
            print('quamrana', s2 - s1)
        
        if __name__ == '__main__':
            eval(f'{sys.argv[1]}()')
        

        在 2^20、2^21、2^22 上的表现

        (base) xxx@xxx:~$ python test.py f1 20
        Kapil 2.0021448135375977
        (base) xxx@xxx:~$ python test.py f2 20
        fsimonjetz 2.720785617828369
        (base) xxx@xxx:~$ python test.py f3 20
        quamrana 0.717628002166748
        (base) xxx@xxx:~$ python test.py f1 21
        Kapil 4.06165337562561
        (base) xxx@xxx:~$ python test.py f2 21
        fsimonjetz 6.2198286056518555
        (base) xxx@xxx:~$ python test.py f3 21
        quamrana 1.563591718673706
        (base) xxx@xxx:~$ python test.py f1 22
        Kapil 8.361187934875488
        (base) xxx@xxx:~$ python test.py f2 22
        fsimonjetz 14.354418992996216
        (base) xxx@xxx:~$ python test.py f3 22
        quamrana 3.355391025543213
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-06-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多