我的解决方案是创建一个生成器列表,乘积矩阵中的每一行一个生成器,然后使用heapq.merge 对这些生成器的输出进行排序。每个生成器在 32 位机器上的大小为 44 字节,因此整个生成器列表仅消耗少量 RAM。
heapq.merge(当没有提供排序键功能时)通过为您传递的每个可迭代对象创建一个 3 元组来工作。该元组包含可迭代的下一个值、可迭代的索引号以及对可迭代的__next__ 方法的引用。它将这些元组放在堆上以执行可迭代值的合并排序。你可以在其 Python source code 中查看详细信息。
因此,我的方法不像 Tim Peters 的解决方案那样节俭,但也不算太简陋,恕我直言。 ;)
def sorted_prod_merge(xs, ys):
''' mergesort generators of the rows. '''
if len(ys) < len(xs):
xs, ys = ys, xs
def gen(x):
for y in ys:
yield x * y
yield from merge(*[gen(x) for x in xs])
这里有一些timeit 代码,它显示了sorted_prod_merge、Tim Peters 的解决方案以及我的一些其他版本的运行时间。我使用了 Tim 的变量名来保持代码统一。有趣的是,Tim 的第一个版本的速度大约是他更高级的解决方案的两倍。我的 sorted_prod_row 运行速度很快,但它是一个可怕的 RAM 猪。
timeit 代码使用itertools recipes 中给出的技术来耗尽迭代器:我们将它提供给长度为零的双端队列。 time_test 代码对每次运行 Timer 的 3 个结果进行排序。这是因为 最小 结果是重要的结果,其他值只是表示测试运行时系统中的差异。有关详细信息,请参阅Timer.repeat 文档中的注释。
from heapq import heappush, heappop, merge
from random import seed, randrange
from timeit import Timer
from collections import deque
seed(163)
# Brute force method, as a generator
def sorted_prod_brute(xs, ys):
yield from sorted(x * y for x in xs for y in ys)
# By Tim Peters
def upprod1(xs, ys):
# xs and ys must be sorted, and non-negative
from heapq import heappush, heappop
# make xs the shorter
if len(ys) < len(xs):
xs, ys = ys, xs
if not xs:
return
lenxs = len(xs)
lenys = len(ys)
# the heap holds 4-tuples:
# (product, xs index, ys index, xs[xs index])
h = [(xs[0] * ys[0], 0, 0, xs[0])]
while h:
prod, xi, yi, x = heappop(h)
yield prod
# same x with next y
yi += 1
if yi < lenys:
heappush(h, (x * ys[yi], xi, yi, x))
# if this is the first time we used x, start
# the next x going
if yi == 1:
xi += 1
if xi < lenxs:
x = xs[xi]
heappush(h, (x * ys[0], xi, 0, x))
# By Tim Peters
def upprod2(xs, ys):
# xs and ys must be sorted, and non-negative
from heapq import heappush, heappop
# make xs the shorter
if len(ys) < len(xs):
xs, ys = ys, xs
if not xs:
return
lenxs = len(xs)
lenys = len(ys)
# the heap holds 3-tuples:
# (product, xs index, ys index)
h = [(xs[0] * ys[0], 0, 0)]
# interior points for which only one immediate predecessor has
# been processed; there's no need to put them in the heap
# until their second predecessor has been processed too
pending = set()
def add(xi, yi):
if xi < lenxs and yi < lenys:
doit = True
if xi and yi: # if either is 0, only one predecessor
p = xi, yi
if p in pending:
pending.remove(p)
else:
pending.add(p)
doit = False
if doit:
heappush(h, (xs[xi] * ys[yi], xi, yi))
while h:
prod, xi, yi = heappop(h)
yield prod
# same x with next y; and same y with next x
add(xi, yi + 1)
add(xi + 1, yi)
assert not pending
def sorted_prod_merge(xs, ys):
''' mergesort generators of the rows. '''
if len(ys) < len(xs):
xs, ys = ys, xs
def gen(x):
for y in ys:
yield x * y
yield from merge(*[gen(x) for x in xs])
def sorted_prod_row(xs, ys):
''' Heapsort, row by row.
Fast, but not space-efficient: the maximum
heap size grows to almost len(ys) * len(xs)
'''
if len(ys) < len(xs):
xs, ys = ys, xs
if not xs:
return
x, xs = xs[0], xs[1:]
heap = []
#big = 0
for y in ys:
lo = x * y
while heap and heap[0] <= lo:
yield heappop(heap)
yield lo
for u in xs:
heappush(heap, u * y)
#big = max(big, len(heap))
#print(big)
while heap:
yield heappop(heap)
def sorted_prod_diag(xs, ys):
''' Heapsort, going along the diagonals
50% slower than sorted_prod_row, but more
space-efficient: the maximum heap size
grows to around 0.5 * len(ys) * len(xs)
'''
if not (xs and ys):
return
lenxs, lenys = len(xs), len(ys)
heap = []
#big = 0
for n in range(lenxs + lenys - 1):
row = sorted(xs[n - i] * ys[i]
for i in range(max(0, n + 1 - lenxs), min(lenys, n + 1)))
lo = row[0]
while heap and heap[0] <= lo:
yield heappop(heap)
yield lo
for u in row[1:]:
heappush(heap, u)
#big = max(big, len(heap))
#print(big)
#assert not heap
def sorted_prod_block(xs, ys):
''' yield the top left corner, then merge sort
the top row, the left column and the remaining
block. So we end up with max(len(xs), len(ys))
recursively nested calls to merge(). It's ok
for small lists, but too slow otherwise.
'''
if not (xs and ys):
return
x, *xs = xs
y, *ys = ys
yield x * y
row = (y * u for u in xs)
col = (x * v for v in ys)
yield from merge(row, col, sorted_prod_block(xs, ys))
def sorted_prod_blockI(xs, ys):
''' Similar to sorted_prod_block except we use indexing
to avoid creating sliced copies of the lists
'''
lenxs, lenys = len(xs), len(ys)
def sorted_block(xi, yi):
if xi == lenxs or yi == lenys:
return
x, y = xs[xi], ys[yi]
yield x * y
xi, yi = xi + 1, yi + 1
row = (xs[i] * y for i in range(xi, lenxs))
col = (ys[i] * x for i in range(yi, lenys))
yield from merge(row, col, sorted_block(xi, yi))
yield from sorted_block(0, 0)
functions = (
sorted_prod_brute,
upprod1,
upprod2,
sorted_prod_merge,
#sorted_prod_row,
sorted_prod_diag,
#sorted_prod_block,
#sorted_prod_blockI,
)
UB = 1000
def verify(numtests, maxlen=10):
print('Verifying. maxlen =', maxlen)
for k in range(numtests):
lenxs = randrange(maxlen + 1)
lenys = randrange(maxlen + 1)
print(k, ':', lenxs, '*', lenys, '=', lenxs * lenys)
xs = sorted(randrange(UB) for i in range(lenxs))
ys = sorted(randrange(UB) for i in range(lenys))
good = list(sorted_prod_brute(xs, ys))
for func in functions[1:]:
result = list(func(xs, ys))
if result != good:
print(func.__name__, 'failed!')
print()
def time_test(loops=20):
timings = []
for func in functions:
# Consume the generator output by feeding it to a zero-length deque
t = Timer(lambda: deque(func(xs, ys), maxlen=0))
result = sorted(t.repeat(3, loops))
timings.append((result, func.__name__))
timings.sort()
for result, name in timings:
print('{:18} : {:.6f}, {:.6f}, {:.6f}'.format(name, *result))
print()
verify(10, 10)
verify(20, 100)
print('\nTimings')
loops = 8192
minlen = 5
for k in range(6):
lenxs = randrange(minlen, 2 * minlen)
lenys = randrange(minlen, 2 * minlen)
print(k, ':', loops, 'loops.', lenxs, '*', lenys, '=', lenxs * lenys)
xs = sorted(randrange(UB) for i in range(lenxs))
ys = sorted(randrange(UB) for i in range(lenys))
time_test(loops)
minlen *= 2
loops //= 4
这是我古老的 2GHz 32 位单核机器上的输出,在 Linux 的旧 Debian 衍生发行版上运行 Python 3.6.0。 YMMV。
Verifying. maxlen = 10
0 : 8 * 9 = 72
1 : 9 * 0 = 0
2 : 1 * 7 = 7
3 : 8 * 10 = 80
4 : 10 * 5 = 50
5 : 10 * 0 = 0
6 : 5 * 2 = 10
7 : 5 * 10 = 50
8 : 3 * 0 = 0
9 : 0 * 6 = 0
Verifying. maxlen = 100
0 : 64 * 0 = 0
1 : 77 * 96 = 7392
2 : 24 * 13 = 312
3 : 53 * 39 = 2067
4 : 74 * 39 = 2886
5 : 92 * 97 = 8924
6 : 31 * 48 = 1488
7 : 39 * 17 = 663
8 : 42 * 25 = 1050
9 : 94 * 25 = 2350
10 : 82 * 83 = 6806
11 : 2 * 97 = 194
12 : 90 * 30 = 2700
13 : 93 * 24 = 2232
14 : 91 * 37 = 3367
15 : 24 * 86 = 2064
16 : 70 * 15 = 1050
17 : 2 * 4 = 8
18 : 72 * 58 = 4176
19 : 25 * 84 = 2100
Timings
0 : 8192 loops. 7 * 8 = 56
sorted_prod_brute : 0.659312, 0.665853, 0.710947
upprod1 : 1.695471, 1.705061, 1.739299
sorted_prod_merge : 1.990161, 1.991129, 2.001242
sorted_prod_diag : 3.013945, 3.018927, 3.053115
upprod2 : 3.582396, 3.586332, 3.622949
1 : 2048 loops. 18 * 16 = 288
sorted_prod_brute : 0.826128, 0.840111, 0.863559
upprod1 : 2.240931, 2.241636, 2.244615
sorted_prod_merge : 2.301838, 2.304075, 2.306918
sorted_prod_diag : 3.030672, 3.053302, 3.135322
upprod2 : 4.860378, 4.949804, 4.953891
2 : 512 loops. 39 * 32 = 1248
sorted_prod_brute : 0.907932, 0.918692, 0.942830
sorted_prod_merge : 2.559567, 2.561709, 2.604387
upprod1 : 2.700482, 2.701147, 2.757695
sorted_prod_diag : 2.961776, 2.965271, 2.995747
upprod2 : 5.563303, 5.654425, 5.656695
3 : 128 loops. 68 * 70 = 4760
sorted_prod_brute : 0.823448, 0.827748, 0.835049
sorted_prod_merge : 2.591373, 2.592134, 2.685534
upprod1 : 2.760466, 2.763615, 2.795082
sorted_prod_diag : 2.789673, 2.828662, 2.848498
upprod2 : 5.483504, 5.488450, 5.517847
4 : 32 loops. 122 * 156 = 19032
sorted_prod_brute : 0.873736, 0.880958, 0.892846
sorted_prod_merge : 2.701089, 2.742456, 2.818822
upprod1 : 2.875358, 2.881793, 2.922569
sorted_prod_diag : 2.953450, 2.988184, 3.012430
upprod2 : 5.780552, 5.812967, 5.826775
5 : 8 loops. 173 * 309 = 53457
sorted_prod_brute : 0.711012, 0.711816, 0.721627
sorted_prod_merge : 1.997386, 1.999774, 2.033489
upprod1 : 2.137337, 2.172369, 3.335119
sorted_prod_diag : 2.324447, 2.329552, 2.331095
upprod2 : 4.278704, 4.289019, 4.324436