【问题标题】:Finding a tuple in large a very large list在一个非常大的列表中查找一个元组
【发布时间】:2021-06-20 01:35:54
【问题描述】:

假设我有一个元组列表:

tuple_library = [('a', 'z', '1'), ('r', '3', 'b'), ('m', '1', 'l')]

我要做的是检查 tuple_library 中是否存在以下元组。

search_list = [('a','a','1'), ('m', '1', 'l')]


def search_the_tupple(t_lib, s_list):
    for item in t_lib:
        if item in s_list:
           return(item)

print(search_the_tupple(tuple_library, search_list))

如果 tuple_library 和 search_list 很小,此代码可以正常工作,但随着这两个项目的增加,完成它所需的时间越长。

我们如何解决这个问题?

【问题讨论】:

  • 排序然后二分查找?
  • 如果您需要重复执行此操作,请将其放在一组中。另外:修复您的编码错误 - 这根本不会运行。
  • 如果你有一个字符串元组,或者只有可散列对象的元组,set 是要走的路。
  • @0 因为它没有运行。将较大的列表放入集合中会使查找常数时间 O(1),从而减轻列表的 O(N)。通过 set-intersection 将两者放在一个集合中会使其更快。这也会为您提供 all 交集,而不仅仅是第一个,因为上面的代码如果固定会提供
  • 设置交集是可行的方法,只要您不经常需要索引并且不关心重复。

标签: python list tuples


【解决方案1】:

创建字典。使用 tuples_library 中的元组作为字典键。然后检查搜索列表中的元组是否作为键存在于字典中。

In what case would I use a tuple as a dictionary key?

tuple_search_dict = {
    ('a', 'z', '1'): ["present"],
    ('r', '3', 'b'): ["present"],
    ('m', '1', 'l'): ["present"],
}

tuple_library = [('a', 'z', '1'), ('r', '3', 'b'), ('m', '1', 'l')]
search_list = [('a', 'a', '1'), ('m', '1', 'l')]

for each in search_list:
    try:
        tuple_search_dict[each]
        print(f"This tuple EXISTS in the list: {each}")
    except KeyError:
        print(f"This tuple DOES NOT EXIST in your list: {each}")

【讨论】:

  • 这只是复制了set 的建议。
  • 没有复制,只是检查密钥是否存在。添加示例代码
【解决方案2】:

首先,让我们分析在另一个元组列表中搜索一个元组列表的标准方法的时间复杂度,每个元组包含 n 个元素。

现在,首先要将 2 个元组发音为相等,我们需要将一个元组的 n 个元素与另一个元组的 n 个元素进行比较,这需要 n 次比较。

现在,我们必须对 search_list 中的所有元素都执行此操作 - 假设是 m。

现在,我们必须对 tuple_library 中的所有元素执行上述 2 个操作 - 即 p。

所以,这里的操作总数是n * m * p。

现在,如果 tuple_library 很大,那么这个搜索操作所花费的时间非常长。

为了减少这种情况,我们可以遵循以下方法:

  1. 首先,获取 search_list 的第一个元组('a','a','1'),并将其第一个元素(在本例中为'a')与 tuple_library 中所有元组的第一个元素进行比较.
  2. 将匹配的元组放入哈希表或字典中,其中键是第一个元素 ('a'),值是匹配元组的列表,然后从原始 search_list 中删除这些元组。
  3. 现在,继续处理该元组中的下一个元素(再次为“a”),并将其与上述列表中的所有元素进行比较。这样一来,您搜索的列表就会小得多。
  4. 对于所有匹配的元素,将它们放入字典中,键是连接两个元素(在本例中为“aa”),然后从前面的列表中删除它们,依此类推,直到匹配到所有元素那个元组。 5.完成此操作后,您的字典将有 3 个键('a'、'aa' 和 'aa1'),每个键都包含包含它们的元组列表。
  5. 接下来,您继续到 search_list 中的下一个元组,并获取它的第一个元素 - 如果该元素存在于字典中,然后获取下一个元素 - 查看它们是否都存在 - 依此类推,直到匹配所有元组中的元素,或者您发现不匹配,在这种情况下,您获取由所有匹配元素组成的键所引用的列表并在那里搜索。
  6. 如果元组的第一个元素没有出现在字典中,则重新开始该过程。

这个过程的结果是,对于 search_list 中的每个后续元组,您必须从元组库中搜索越来越少的元组。因此,您必须搜索 log(n) 个元素,而不是搜索 n 个元素。

因此,您的总运行时间变为 log(n) * m * p。现在,由于这里最大的列表是元组库,因此减少那里的搜索次数将大大降低您的时间复杂度。

【讨论】:

    【解决方案3】:

    最后,使用set。如果可能,请提前计算集合。

    第一列是原始方法。二等分搜索。第三组路口。第四组与预计算集的交集。

    请注意,10k 和 100k 样本大小之间的第一列和第四列的差异。 由于样本量大于 100k 样本中的样本空间,因此线性搜索next((s for s in t_lib if s in intersect), None) 会消耗更多时间。这是因为输入项​​不是唯一的。

    demo       0.14000 0.36631 0.43345 0.32987
    Sample size 1
    avg        0.20465 0.51461 0.78238 0.62021
    min        0.20071 0.45612 0.77142 0.60785
    max        0.20777 0.53482 0.79569 0.63588
    Sample size 10
    avg        0.29292 0.36985 0.15805 0.10697
    min        0.29023 0.33839 0.15505 0.10555
    max        0.29462 0.38843 0.16004 0.10892
    Sample size 100
    avg        2.25065 0.57967 0.09441 0.05153
    min        0.31502 0.22779 0.07239 0.02602
    max        2.48248 0.62714 0.09965 0.05538
    Sample size 1000
    avg        1.04594 0.35947 0.06731 0.01942
    min        0.10262 0.33380 0.06457 0.01773
    max        3.87742 0.43988 0.07285 0.02422
    Sample size 10000
    avg        0.05340 0.48607 0.11466 0.03703
    min        0.00144 0.47951 0.11063 0.03608
    max        0.17092 0.49262 0.12124 0.03801
    Sample size 100000
    avg        0.01492 0.63902 0.18956 0.04588
    min        0.00152 0.63142 0.18007 0.04313
    max        0.03624 0.65317 0.21257 0.05027
    Sample size 1000000
    avg        0.00236 2.98343 0.71825 0.02207
    min        0.00008 2.93934 0.69734 0.01961
    max        0.00484 3.05418 0.74362 0.02691
    

    代码:

    from bisect import bisect_left
    from functools import partial
    from random import Random
    from string import ascii_lowercase, digits
    from timeit import timeit
    
    LETTERS = ascii_lowercase + digits
    
    
    def next_triplet(r=Random(123456)):  # fixed seed
        return tuple(r.choices(LETTERS, k=3))  # len(sample space) = 36 ** 3 = 46656
    
    
    def search_the_tupple(t_lib, s_list):
        for item in t_lib:
            if item in s_list:
                return (item)
    
    
    def search_the_tupple_bisect(t_lib, s_list):
        s_list = sorted(s_list)
        size = len(s_list)
    
        for item in t_lib:
            i = bisect_left(s_list, item)
    
            if i < size and s_list[i] == item:
                return item
    
    
    def search_the_tupple_set(t_lib, s_list):
        intersect = set(t_lib).intersection(s_list)
        return next((s for s in t_lib if s in intersect), None)
    
    
    def search_the_tupple_set_precomputed(t_lib, t_set, s_set):
        intersect = t_set.intersection(s_set)
        return next((s for s in t_lib if s in intersect), None)
    
    
    def run(tuple_library, search_list):
        number = max(5, int(3 * 1000000 / (len(tuple_library) + len(search_list))))  # keep the runtime manageable
    
        functions = (
            # pack the test function and arguments together
            partial(search_the_tupple, tuple_library, search_list),
            partial(search_the_tupple_bisect, tuple_library, search_list),
            partial(search_the_tupple_set, tuple_library, search_list),
            partial(search_the_tupple_set_precomputed, tuple_library, set(tuple_library), set(search_list)),
        )
    
        results = [f() for f in functions]
        assert len(set(results)) == 1, results  # make sure no error
    
        return [timeit(f, number=number) for f in functions]
    
    
    def main():
        def print_timing(title, timings):
            print(f'{title:10s}', ' '.join(f'{t:.5f}' for t in timings))
    
        tuple_library = [('a', 'z', '1'), ('r', '3', 'b'), ('m', '1', 'l')]
        search_list = [('a', 'a', '1'), ('m', '1', 'l')]
    
        print_timing('demo', run(tuple_library, search_list))
    
        for sample_size in [10 ** s for s in range(7)]:  # 10 ** 5 = 100_000, which is greater than the sample space
            print('Sample size', sample_size)
    
            timing_results = []
    
            for run_index in range(10):
                tuple_library = [next_triplet() for _ in range(sample_size)]
                search_list = [next_triplet() for _ in range(sample_size)]
                timing_results.append(run(tuple_library, search_list))
    
            timing_min = []
            timing_max = []
            timing_avg = []
    
            for fn_index, fn_timings in enumerate(zip(*timing_results)):
                timing_avg.append(sum(fn_timings) / len(fn_timings))
                timing_min.append(min(fn_timings))
                timing_max.append(max(fn_timings))
    
            print_timing('avg', timing_avg)
            print_timing('min', timing_min)
            print_timing('max', timing_max)
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

      【解决方案4】:

      我只需将元组列表拆分为 N 个子列表,其中 N 是我计算机上的处理器数量,创建一个大小为 N 的多处理池,然后并行搜索 N 个子列表。我还将使用集合 search_list 初始化池中的每个进程,以便测试子列表的元素是否是所寻找的元素之一成为 O(1) 操作(如果 search_list 实际上大于2 个元素)。 然后worker函数search_the_tuple将传入的列表转换为集合,并与search_list进行集合交集并返回结果。一旦第一个并行进程发现非零长度集合返回,终止剩余进程。

      问题是,将子列表转换为集合并执行交集(由库例程完成)是否会比循环遍历子列表和测试集合成员(主要是 Python 字节码)快得多.

      import multiprocessing as mp
      
      
      def init_pool(s_list):
          global search_list
          search_list = set(s_list)
      
      def search_the_tuple(t_sub_lib):
          for item in t_sub_lib:
              if item in search_list:
                 return(item)
      
      
      def split(lst, n):
          # a generator expression to split the library of tuples into n (narly) equal sub lists:
          k, m = divmod(len(lst), n)
          return (lst[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n))
      
      
      def main():
      
          search_list = [('a','a','1'), ('m', '1', 'l')]
      
          tuple_library = [
              ('g', 'z', '1'), ('h', '3', 'b'), ('i', 'a', 'l'), ('j', 'z', '1'), ('k', '3', 'b'), ('l', '1', 'l'),
              ('m', 'z', '1'), ('n', '3', 'b'), ('o', '1', 'l'), ('p', 'z', '1'), ('q', '3', 'b'), ('r', '1', 'l'),
              ('a', 'z', '1'), ('r', '3', 'b'), ('m', '1', 'l'), ('d', 'z', '1'), ('e', '3', 'b'), ('f', '1', 'l')
          ]
      
          n_processors = mp.cpu_count()
          with mp.Pool(n_processors, initializer=init_pool, initargs=(search_list,)) as pool:
              t_sub_libs = split(tuple_library, n_processors)
              for t in pool.imap_unordered(search_the_tuple, t_sub_libs):
                  if t is not None:
                      print(t)
                      break
          # When we exit this block, terminate will be called on pool and
          # all processes will be killed.
      
      # required for Windows:
      if __name__ == '__main__':
          main()
      

      打印:

      ('m', '1', 'l')
      

      更新

      我做了一个小基准测试,看看是否值得将列表转换为集合,然后执行交集。在这个基准测试中,我有一个包含 20,000 个元素的列表,并且为了让转换为集合具有优势,我正在寻找的元素位于最后:

      import timeit
      
      s = set([(19999, 19999), (21000, 21000)])
      lst = [(i,i) for i in range(20000)]
      
      def s1():
          for i in lst:
              if i in s:
                  return i
      
      def s2():
          s2 = set(lst)
          return s & s2
      
      print(timeit.timeit('s1()', number=1000, globals=globals()))
      print(timeit.timeit('s2()', number=1000, globals=globals()))
      

      打印:

      0.9409163000000262
      1.8975496000000476
      

      但即使在这里,set-conversion 版本s2 的速度也几乎是原来的两倍。但是,当我要查找的值位于列表中间时(平均而言),我们有:

      import timeit
      
      s = set([(10000, 10000), (21000, 21000)])
      lst = [(i,i) for i in range(20000)]
      
      def s1():
          for i in lst:
              if i in s:
                  return i
      
      def s2():
          s2 = set(lst)
          return s & s2
      
      print(timeit.timeit('s1()', number=1000, globals=globals()))
      print(timeit.timeit('s2()', number=1000, globals=globals()))
      

      打印:

      0.5094996000000265
      1.8004526000001988
      

      而且,正如您所料,s1 现在的运行速度几乎是原来的两倍。

      结论

      1. 您绝对应该将search_list 转换为set,因为它会被多次搜索。这可能是您唯一可用的优化。
      2. 您仅指定tuple_library 非常大。这是相当不具体的。我的基准建议将您的tuple_library 转换为集合是有利的,因为它只被搜索一次。
      3. 虽然,我已经展示了如何使用多处理“分而治之”,但我并不清楚(再次不知道您的 tuple_library 实际上有多大,甚至可能无关紧要)多处理将完全提高性能(它甚至可能会损害性能)。仅创建进程池会产生开销,然后将子列表传递给“生活”在不同地址空间中的进程会再次产生开销。您只需尝试一下,看看是否有帮助。

      更新 2

      @lllrnr101 所做的基准测试存在问题。我会提出这个作为评论,但不幸的是,这对于这样的人来说太长了。首先,函数normal_search_multiple_tuples_in_tuples_list应该编码为:

      def normal_search_multiple_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
          s = set(tuples_to_search)
          for t in big_tuple_list:
              if t in s:
                  return t
      

      请注意,在 OP 的问题中,OP 在找到第一个匹配项后返回,并且 尝试查找所有匹配项。至少将tuples_to_search 转换为一个集合可能更有效,因为它将被搜索多次。

      其次,真正需要比较的只有两个策略是normal_search_multiple_tuples_in_tuples_listintersect_search_tuples_in_tuples_list。后面的基准测试涉及首先将列表转换为集合并搜索它,没有正确考虑转换成本,并且不对应实际问题(将列表转换为集合打印的时间是单个成本转换,而其他时间打印为 100000 次迭代)。

      第三,OP 指定有问题的列表非常大,不管这意味着什么,但我认为它大于 10 和 100,可能还有 1000。所以应该针对更大的列表,timeit 进行更少的迭代,以便在合理的时间内完成。这导致以下行为:

      import timeit
      import time
      import random
      import string
      from random import randint
      from random import sample
      
      data_values = string.ascii_lowercase + string.digits
      len_data_values = len(data_values)
      random_lower_limit = 0
      random_upper_limit = len_data_values - 1
      
      def normal_search_multiple_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
          s = set(tuples_to_search)
          for t in big_tuple_list:
              if t in s:
                  return t
      
      
      def intersect_search_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
          tuples_to_search_set = set(tuples_to_search)
          big_tuple_set = set(big_tuple_list)
          return tuples_to_search_set.intersection(big_tuple_set)
      
      
      def get_big_data(big_tuple_list_size):
          big_tuple_list = [(
                              data_values[randint(random_lower_limit, random_upper_limit)],
                              data_values[randint(random_lower_limit, random_upper_limit)],
                              data_values[randint(random_lower_limit, random_upper_limit)]
                          ) for _ in range(big_tuple_list_size)]
          return big_tuple_list
      
      def get_small_data(big_tuple_list, tuples_to_search_size):
          return sample(big_tuple_list, tuples_to_search_size)
      
      def get_required_data(big_tuple_list_size, tuples_to_search_size):
          big_tuple_list = get_big_data(big_tuple_list_size)
          tuples_to_search = get_small_data(big_tuple_list, tuples_to_search_size)
          return (big_tuple_list, tuples_to_search)
      
      
      if __name__ == '__main__':
      
          test_combinations = [(10000, 5), (20000, 5), (100000, 5)]
      
          functions_to_test = [
                              normal_search_multiple_tuples_in_tuples_list,
                              intersect_search_tuples_in_tuples_list
                              ]
      
          for big_tuple_list_size, tuples_to_search_size in test_combinations:
              tuples_to_search, big_tuple_list = get_required_data(big_tuple_list_size, tuples_to_search_size)
      
              print(f'For a run of searching {tuples_to_search_size} needles in {big_tuple_list_size} size haystack')
              for func in functions_to_test:
                  print(f'''Time taken by {func.__name__}: {timeit.timeit('func(tuples_to_search, big_tuple_list)', number=1000, globals=globals())}''')
              print()
      

      打印:

      For a run of searching 5 needles in 10000 size haystack
      Time taken by normal_search_multiple_tuples_in_tuples_list: 0.681931
      Time taken by intersect_search_tuples_in_tuples_list: 0.5806513
      
      For a run of searching 5 needles in 20000 size haystack
      Time taken by normal_search_multiple_tuples_in_tuples_list: 0.8495027999999998
      Time taken by intersect_search_tuples_in_tuples_list: 0.8799418999999999
      
      For a run of searching 5 needles in 100000 size haystack
      Time taken by normal_search_multiple_tuples_in_tuples_list: 8.51018
      Time taken by intersect_search_tuples_in_tuples_list: 8.115366799999999
      

      时间上没有太大的区别。我自己的基准测试表明,其中一个寻找的项目是列表的中间元素,这表明不转换列表的性能明显优于将列表转换为集合。 但是这个基准和我的最大区别在于我的基准不会将tuples_to_search 列表转换为集合;这个集合是预先创建的,因为 OP 知道他们在寻找什么。 如果我们从我们拥有的基准中删除创建该集合的成本:

      import timeit
      import time
      import random
      import string
      from random import randint
      from random import sample
      
      data_values = string.ascii_lowercase + string.digits
      len_data_values = len(data_values)
      random_lower_limit = 0
      random_upper_limit = len_data_values - 1
      
      def normal_search_multiple_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
          for t in big_tuple_list:
              if t in tuples_to_search:
                  return t
      
      
      def intersect_search_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
          big_tuple_set = set(big_tuple_list)
          return tuples_to_search.intersection(big_tuple_set)
      
      
      def get_big_data(big_tuple_list_size):
          big_tuple_list = [(
                              data_values[randint(random_lower_limit, random_upper_limit)],
                              data_values[randint(random_lower_limit, random_upper_limit)],
                              data_values[randint(random_lower_limit, random_upper_limit)]
                          ) for _ in range(big_tuple_list_size)]
          return big_tuple_list
      
      def get_small_data(big_tuple_list, tuples_to_search_size):
          return sample(big_tuple_list, tuples_to_search_size)
      
      def get_required_data(big_tuple_list_size, tuples_to_search_size):
          big_tuple_list = get_big_data(big_tuple_list_size)
          tuples_to_search = get_small_data(big_tuple_list, tuples_to_search_size)
          return (big_tuple_list, tuples_to_search)
      
      
      if __name__ == '__main__':
      
          test_combinations = [(10000, 5), (20000, 5), (100000, 5)]
      
          functions_to_test = [
                              normal_search_multiple_tuples_in_tuples_list,
                              intersect_search_tuples_in_tuples_list
                              ]
      
          for big_tuple_list_size, tuples_to_search_size in test_combinations:
              tuples_to_search, big_tuple_list = get_required_data(big_tuple_list_size, tuples_to_search_size)
      
              print(f'For a run of searching {tuples_to_search_size} needles in {big_tuple_list_size} size haystack')
              s = set(tuples_to_search)
              for func in functions_to_test:
                  print(f'''Time taken by {func.__name__}: {timeit.timeit('func(s, big_tuple_list)', number=1000000, globals=globals())}''')
              print()
      

      打印:

      For a run of searching 5 needles in 10000 size haystack
      Time taken by normal_search_multiple_tuples_in_tuples_list: 0.16306289999999998
      Time taken by intersect_search_tuples_in_tuples_list: 0.6508408
      
      For a run of searching 5 needles in 20000 size haystack
      Time taken by normal_search_multiple_tuples_in_tuples_list: 0.16933260000000006
      Time taken by intersect_search_tuples_in_tuples_list: 0.6473307000000001
      
      For a run of searching 5 needles in 100000 size haystack
      Time taken by normal_search_multiple_tuples_in_tuples_list: 0.1777871999999996
      Time taken by intersect_search_tuples_in_tuples_list: 0.7130949000000002
      

      您可以清楚地看到您不应该将列表转换为集合。最后,如果这不令人信服,这是我的基准测试,我在其中对我们尝试在搜索函数中匹配的列表元素进行集合转换,并且我们正在搜索的列表中与这些元素之一匹配的元素是最后一个元素.这应该给使用集合交集的方法带来最大的优势:

      import timeit
      
      
      def s1(s, lst):
          s = set(s)
          for i in lst:
              if i in s:
                  return i
      
      def s2(s, lst):
          s = set(s)
          s2 = set(lst)
          return s & s2
      
      
      lst = [(i,i) for i in range(20000)]
      s = [(19999, 19999), (21000, 21000)] # match will be last element of lst
      # look for a s in lst:
      print(timeit.timeit('s1(s, lst)', number=1000, globals=globals()))
      print(timeit.timeit('s2(s, lst)', number=1000, globals=globals()))
      

      打印:

      0.887862600000517
      1.8508867000000464
      

      【讨论】:

        【解决方案5】:

        你有以下方法可以得到答案:

        1. 使用 needle in haystack 运算符进行普通的普通搜索。
        2. 将您的大元组干草堆转换为集合和look into set
        3. 将您的针头和干草堆都转换成一组并返回intersection

        正如您将在最后的统计数据中看到的那样,随着您的干草堆大小增加,方法 1 变得越来越慢。 2 和 3 都更好,其中 3 优于 2。

        事实上,如果您在程序执行期间仅将 haystack 转换为一个集合,然后将其用于方法 3(集合交集),您将获得最佳性能。

        我没有包括在 O(nlogn) 中对大列表进行排序,然后使用 bisect O(mlogn) 在其中进行搜索,因为这不会比方法 3 O(m)
        这是用于查找相对性能的示例代码。输入您的样本量,并亲自查看它在您的机器配置上的表现。

        import timeit
        import time
        import random
        import string
        from random import randint
        from random import sample
        
        data_values = string.ascii_lowercase + string.digits
        len_data_values = len(data_values)
        random_lower_limit = 0
        random_upper_limit = len_data_values - 1
        
        def normal_search_multiple_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
            ans = set()
            for single_tuple in tuples_to_search:
                if single_tuple in big_tuple_list:
                    ans.add(single_tuple)
            return ans
        
        def set_conversion_search_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
            big_tuple_set = set(big_tuple_list)
            ans = set()
            for single_tuple in tuples_to_search:
                if single_tuple in big_tuple_set:
                    ans.add(single_tuple)
            return ans
        
        def intersect_search_tuples_in_tuples_list(tuples_to_search, big_tuple_list):
            tuples_to_search_set = set(tuples_to_search)
            big_tuple_set = set(big_tuple_list)
            return tuples_to_search_set.intersection(big_tuple_set)
        
        def search_in_converted_set(tuples_to_search, big_tuple_set):
            ans = set()
            for single_tuple in tuples_to_search:
                if single_tuple in big_tuple_set:
                    ans.add(single_tuple)
            return ans
        
        def intersect_search_tuples_in_converted_set(tuples_to_search, big_tuple_set):
            tuples_to_search_set = set(tuples_to_search)
            return tuples_to_search_set.intersection(big_tuple_set)
            
        def get_big_data(big_tuple_list_size):
            big_tuple_list = [(
                                data_values[randint(random_lower_limit, random_upper_limit)], 
                                data_values[randint(random_lower_limit, random_upper_limit)], 
                                data_values[randint(random_lower_limit, random_upper_limit)]
                            ) for _ in range(big_tuple_list_size)]
            return big_tuple_list
        
        def get_small_data(big_tuple_list, tuples_to_search_size):
            return sample(big_tuple_list, tuples_to_search_size)
        
        def get_required_data(big_tuple_list_size, tuples_to_search_size):
            big_tuple_list = get_big_data(big_tuple_list_size)
            tuples_to_search = get_small_data(big_tuple_list, tuples_to_search_size)
            return (big_tuple_list, tuples_to_search)
        
        def convert_list_to_set(big_list):
            return set(big_list)
        
        if __name__ == '__main__':
        
            test_combinations = [(10, 5), (100, 5), (1000, 5)]
        
            functions_to_test = [
                                normal_search_multiple_tuples_in_tuples_list,
                                set_conversion_search_tuples_in_tuples_list,
                                intersect_search_tuples_in_tuples_list
                                ]
        
            for big_tuple_list_size, tuples_to_search_size in test_combinations:
                tuples_to_search, big_tuple_list = get_required_data(big_tuple_list_size, tuples_to_search_size)
        
                print(f'For a run of searching {tuples_to_search_size} needles in {big_tuple_list_size} size haystack')
                for func in functions_to_test:
                    print(f'''Time taken by {func.__name__}: {timeit.timeit('func(tuples_to_search, big_tuple_list)', globals=globals())}''')
                print()
        
            big_tuple_list = get_big_data(1000000)
        
            before = time.time()
            big_tuple_set = convert_list_to_set(big_tuple_list)
            print(f'Time taken for one-time job of converting list to set: {time.time() - before}')
        
            for small_sample_size in [5, 10, 50, 100]:
                tuples_to_search = get_small_data(big_tuple_list, small_sample_size)
                for func in [search_in_converted_set, intersect_search_tuples_in_converted_set]:
                    print(f'''Time taken by {func.__name__} for searching {small_sample_size} tuples: {timeit.timeit('func(tuples_to_search, big_tuple_set)', globals=globals())}''')
                print()
        

        对于我的机器,我得到以下统计信息(timeit 默认运行 1M 操作,如果要更改,请指定 numbers 参数。)1M 项目的内存占用没有超过 85M。

        For a run of searching 5 needles in 10 size haystack
        Time taken by normal_search_multiple_tuples_in_tuples_list: 1.972483009998541
        Time taken by set_conversion_search_tuples_in_tuples_list: 1.5855916219989012
        Time taken by intersect_search_tuples_in_tuples_list: 1.448762740001257
        
        For a run of searching 5 needles in 100 size haystack
        Time taken by normal_search_multiple_tuples_in_tuples_list: 18.045348233001278
        Time taken by set_conversion_search_tuples_in_tuples_list: 6.803115938000701
        Time taken by intersect_search_tuples_in_tuples_list: 6.169837320001534
        
        For a run of searching 5 needles in 1000 size haystack
        Time taken by normal_search_multiple_tuples_in_tuples_list: 177.56795450999925
        Time taken by set_conversion_search_tuples_in_tuples_list: 56.36051504599891
        Time taken by intersect_search_tuples_in_tuples_list: 46.09082598700115
        
        Time taken for one-time job of converting list to set: 0.17918157577514648
        Time taken by search_in_converted_set for searching 5 tuples: 1.241585361000034
        Time taken by intersect_search_tuples_in_converted_set for searching 5 tuples: 1.01804721499866
        
        Time taken by search_in_converted_set for searching 10 tuples: 2.011633182002697
        Time taken by intersect_search_tuples_in_converted_set for searching 10 tuples: 1.3614019449996704
        
        Time taken by search_in_converted_set for searching 50 tuples: 9.395802918999834
        Time taken by intersect_search_tuples_in_converted_set for searching 50 tuples: 5.388387926999712
        
        Time taken by search_in_converted_set for searching 100 tuples: 19.021971608002787
        Time taken by intersect_search_tuples_in_converted_set for searching 100 tuples: 12.412382661001175
        

        【讨论】:

        • 我有一些关于你的基准的 cmets,它们太长了,无法在此处包含,但你可以看到 Update 2 对我的回答。
        【解决方案6】:
        1. tuple_librarysearch_list 转换为带有set() 的python 集
        2. 返回两个集合的交集,即tuple_librarysearch_list中的所有元素
        tuple_library = [('a', 'z', '1'), ('r', '3', 'b'), ('m', '1', 'l')]
        search_list = [('a','a','1'), ('m', '1', 'l')]
        
        
        def search_the_tupple(t_lib, s_list):
            return set(t_lib).intersection(set(s_list))
        
        
        print(search_the_tupple(tuple_library, search_list))
        

        这是假设您希望保留元组的顺序。 (这样('m', '1', 'l') 会出现,但('m', 'l', '1') 不会出现。

        仅供参考:无论您是使用t_lib.intersection(s_list) 还是其他方式都没有关系。

        【讨论】:

        • 要在OP中获得结果,请使用next((s for s in t_lib if s in the_intersection), None)
        猜你喜欢
        • 2018-07-15
        • 2015-12-01
        • 1970-01-01
        • 2021-10-09
        • 2019-03-25
        • 2021-09-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多