Python 中的二维列表中的二进制搜索和 return 列表

Binary search and return list from a 2D list in Python

我是 Python 编程的新手,我已经尝试这个问题很长时间了,但我无法想出解决方案。问题是我得到了一份员工 ID 列表和员工的出生年份,我应该 return 一份在输入年份出生的员工 ID 列表。以下是列表的示例。

lst = [[2, 1987], [4, 1990], [6, 1992]...]

线性搜索需要太多时间,因为列表中有 100 万员工。因此,我认为这道题需要我们使用二分查找。我应该想出一个函数,它需要输入年份和 lst 来得到一个 returns 员工 ID 的列表。如果当年没有出生的员工,它应该 return 一个空列表。下面是一个函数的例子。

输入 > get_IDs(2012)

输出 > [102, 204, 199632, 199649]

我知道我可以为一维数组执行内置的平分函数,但我不确定如何对二维数组执行二进制搜索。请就我应该做什么提出建议,非常感谢任何建议!

首先,一百万个元素在现代硬件上并不算多,因此使用过滤/列表可能会非常快。让我们先设置一些测试数据:

import random, bisect, operator
random.seed(0)

years = list(range(1950, 2003))
N = 1_000_000

employees = sorted([(i, random.choice(years)) for i in range(N)],
                  key=operator.itemgetter(1))

target_year = 1980
%timeit emp_1980 = [i for i, y in employees if y == target_year]
# 1 loop, best of 3: 261 ms per loop
# can query 4 years per second

您可以将内置 bisect 与列表而不是标量一起使用,但默认情况下它会比较第一个元素 (ID),而不是我们想要的年份。我们可以通过一些预处理来解决这个问题:

import bisect
# all (year, id) pairs sorted by year
year_id_sorted = [[y, i] for i, y in sorted(employees, key=operator.itemgetter(1))]

def ids_from_year(target_y):
  result = list()
  # make use of elementwise list ordering
  i = bisect.bisect_left(year_id_sorted, [target_y, 0])
  for y, ID in year_id_sorted[i:]:
    if y == target_y:
      result.append(ID)
    else:
      break
  return result

%timeit ids_from_year(target_year)
# 100 loops, best of 3: 11.9 ms per loop
# can query 100 years per second

这产生了 26 倍的加速。还不错,但我们已经产生了一些预处理成本并且不得不存储数据集的副本。现在让我们尝试将员工存储在以下形式的字典中:"year => list of employees".

from collections import defaultdict
employee_by_year = defaultdict(list)
for i, y in employees:
  employee_by_year[y].append(i)
# 1 loop, best of 3: 361 ms per loop
# pre-processing step is only %40 more expensive than
# a single lookup in the original case.

%timeit employee_by_year[target_year]
# 10000000 loops, best of 3: 63.2 ns per loop
# can query 16 million years per second
# other factors will likely limit processing speed

我认为最佳实施取决于您计划 运行 查询的频率。 运行 它至少两次证明使用 dict 是合理的。如果您使用的是 less "granular" 指标(例如薪水),则可以证明使用二分查找是合理的。如果您需要查询多个指标,例如年 的薪水,你 运行 陷入内存与速度的权衡。那么,最佳策略实际上取决于您的数据分布。

要使二分查找起作用,您必须有一个排序列表。分类是有代价的;它代表 O(nlogn) 时间复杂度。

但是,如果您使用按年份键入的字典,这可以在没有二进制搜索的情况下完成,具有线性时间复杂度:

from collections import defaultdict 

# preprocessing
dic = defaultdict(list) # silently create an empty list when addressing a year the first time
for id, year in lst:
    dic[year].append(id)

# implementation of the required function 
def get_IDs(year):
    return dic[year]