Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 35db439

Browse files
committed
Issue #13742: Add key and reverse parameters to heapq.merge()
1 parent e7bfe13 commit 35db439

5 files changed

Lines changed: 98 additions & 19 deletions

File tree

Doc/glossary.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,12 +444,13 @@ Glossary
444444

445445
A number of tools in Python accept key functions to control how elements
446446
are ordered or grouped. They include :func:`min`, :func:`max`,
447-
:func:`sorted`, :meth:`list.sort`, :func:`heapq.nsmallest`,
448-
:func:`heapq.nlargest`, and :func:`itertools.groupby`.
447+
:func:`sorted`, :meth:`list.sort`, :func:`heapq.merge`,
448+
:func:`heapq.nsmallest`, :func:`heapq.nlargest`, and
449+
:func:`itertools.groupby`.
449450

450451
There are several ways to create a key function. For example. the
451452
:meth:`str.lower` method can serve as a key function for case insensitive
452-
sorts. Alternatively, an ad-hoc key function can be built from a
453+
sorts. Alternatively, a key function can be built from a
453454
:keyword:`lambda` expression such as ``lambda r: (r[0], r[2])``. Also,
454455
the :mod:`operator` module provides three key function constructors:
455456
:func:`~operator.attrgetter`, :func:`~operator.itemgetter`, and

Doc/library/heapq.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ The following functions are provided:
8181
The module also offers three general purpose functions based on heaps.
8282

8383

84-
.. function:: merge(*iterables)
84+
.. function:: merge(*iterables, key=None, reverse=False)
8585

8686
Merge multiple sorted inputs into a single sorted output (for example, merge
8787
timestamped entries from multiple log files). Returns an :term:`iterator`
@@ -91,6 +91,18 @@ The module also offers three general purpose functions based on heaps.
9191
not pull the data into memory all at once, and assumes that each of the input
9292
streams is already sorted (smallest to largest).
9393

94+
Has two optional arguments which must be specified as keyword arguments.
95+
96+
*key* specifies a :term:`key function` of one argument that is used to
97+
extract a comparison key from each input element. The default value is
98+
``None`` (compare the elements directly).
99+
100+
*reverse* is a boolean value. If set to ``True``, then the input elements
101+
are merged as if each comparison were reversed.
102+
103+
.. versionchanged:: 3.5
104+
Added the optional *key* and *reverse* parameters.
105+
94106

95107
.. function:: nlargest(n, iterable, key=None)
96108

Lib/heapq.py

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ def heapify(x):
176176
for i in reversed(range(n//2)):
177177
_siftup(x, i)
178178

179+
def _heappop_max(heap):
180+
"""Maxheap version of a heappop."""
181+
lastelt = heap.pop() # raises appropriate IndexError if heap is empty
182+
if heap:
183+
returnitem = heap[0]
184+
heap[0] = lastelt
185+
_siftup_max(heap, 0)
186+
return returnitem
187+
return lastelt
188+
179189
def _heapreplace_max(heap, item):
180190
"""Maxheap version of a heappop followed by a heappush."""
181191
returnitem = heap[0] # raises appropriate IndexError if heap is empty
@@ -311,7 +321,7 @@ def _siftup_max(heap, pos):
311321
except ImportError:
312322
pass
313323

314-
def merge(*iterables):
324+
def merge(*iterables, key=None, reverse=False):
315325
'''Merge multiple sorted inputs into a single sorted output.
316326
317327
Similar to sorted(itertools.chain(*iterables)) but returns a generator,
@@ -321,31 +331,73 @@ def merge(*iterables):
321331
>>> list(merge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25]))
322332
[0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25]
323333
334+
If *key* is not None, applies a key function to each element to determine
335+
its sort order.
336+
337+
>>> list(merge(['dog', 'horse'], ['cat', 'fish', 'kangaroo'], key=len))
338+
['dog', 'cat', 'fish', 'horse', 'kangaroo']
339+
324340
'''
325341

326342
h = []
327343
h_append = h.append
344+
345+
if reverse:
346+
_heapify = _heapify_max
347+
_heappop = _heappop_max
348+
_heapreplace = _heapreplace_max
349+
direction = -1
350+
else:
351+
_heapify = heapify
352+
_heappop = heappop
353+
_heapreplace = heapreplace
354+
direction = 1
355+
356+
if key is None:
357+
for order, it in enumerate(map(iter, iterables)):
358+
try:
359+
next = it.__next__
360+
h_append([next(), order * direction, next])
361+
except StopIteration:
362+
pass
363+
_heapify(h)
364+
while len(h) > 1:
365+
try:
366+
while True:
367+
value, order, next = s = h[0]
368+
yield value
369+
s[0] = next() # raises StopIteration when exhausted
370+
_heapreplace(h, s) # restore heap condition
371+
except StopIteration:
372+
_heappop(h) # remove empty iterator
373+
if h:
374+
# fast case when only a single iterator remains
375+
value, order, next = h[0]
376+
yield value
377+
yield from next.__self__
378+
return
379+
328380
for order, it in enumerate(map(iter, iterables)):
329381
try:
330382
next = it.__next__
331-
h_append([next(), order, next])
383+
value = next()
384+
h_append([key(value), order * direction, value, next])
332385
except StopIteration:
333386
pass
334-
heapify(h)
335-
336-
_heapreplace = heapreplace
387+
_heapify(h)
337388
while len(h) > 1:
338389
try:
339390
while True:
340-
value, order, next = s = h[0]
391+
key_value, order, value, next = s = h[0]
341392
yield value
342-
s[0] = next() # raises StopIteration when exhausted
343-
_heapreplace(h, s) # restore heap condition
393+
value = next()
394+
s[0] = key(value)
395+
s[2] = value
396+
_heapreplace(h, s)
344397
except StopIteration:
345-
heappop(h) # remove empty iterator
398+
_heappop(h)
346399
if h:
347-
# fast case when only a single iterator remains
348-
value, order, next = h[0]
400+
key_value, order, value, next = h[0]
349401
yield value
350402
yield from next.__self__
351403

Lib/test/test_heapq.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from test import support
88
from unittest import TestCase, skipUnless
9+
from operator import itemgetter
910

1011
py_heapq = support.import_fresh_module('heapq', blocked=['_heapq'])
1112
c_heapq = support.import_fresh_module('heapq', fresh=['_heapq'])
@@ -152,11 +153,21 @@ def test_heapsort(self):
152153

153154
def test_merge(self):
154155
inputs = []
155-
for i in range(random.randrange(5)):
156-
row = sorted(random.randrange(1000) for j in range(random.randrange(10)))
156+
for i in range(random.randrange(25)):
157+
row = []
158+
for j in range(random.randrange(100)):
159+
tup = random.choice('ABC'), random.randrange(-500, 500)
160+
row.append(tup)
157161
inputs.append(row)
158-
self.assertEqual(sorted(chain(*inputs)), list(self.module.merge(*inputs)))
159-
self.assertEqual(list(self.module.merge()), [])
162+
163+
for key in [None, itemgetter(0), itemgetter(1), itemgetter(1, 0)]:
164+
for reverse in [False, True]:
165+
seqs = []
166+
for seq in inputs:
167+
seqs.append(sorted(seq, key=key, reverse=reverse))
168+
self.assertEqual(sorted(chain(*inputs), key=key, reverse=reverse),
169+
list(self.module.merge(*seqs, key=key, reverse=reverse)))
170+
self.assertEqual(list(self.module.merge()), [])
160171

161172
def test_merge_does_not_suppress_index_error(self):
162173
# Issue 19018: Heapq.merge suppresses IndexError from user generator

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ Library
9494
error bubble up as this "bad data" appears in many real world zip files in
9595
the wild and is ignored by other zip tools.
9696

97+
- Issue #13742: Added "key" and "reverse" parameters to heapq.merge().
98+
(First draft of patch contributed by Simon Sapin.)
99+
97100
- Issue #21402: tkinter.ttk now works when default root window is not set.
98101

99102
- Issue #3015: _tkinter.create() now creates tkapp object with wantobject=1 by

0 commit comments

Comments
 (0)