Skip to content

Commit eccf484

Browse files
Initial addition
1 parent ddc27f9 commit eccf484

File tree

4 files changed

+132
-18
lines changed

4 files changed

+132
-18
lines changed

Doc/library/heapq.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,46 @@ The following functions are provided:
8282
on the heap.
8383

8484

85+
.. function:: heappush_max(heap, item)
86+
87+
Push the value *item* onto the *heap*, maintaining the heap invariant.
88+
89+
90+
.. function:: heappop_max(heap)
91+
92+
Pop and return the largest item from the *heap*, maintaining the heap
93+
invariant. If the heap is empty, :exc:`IndexError` is raised. To access the
94+
largest item without popping it, use ``heap[0]``.
95+
96+
97+
.. function:: heappushpop_max(heap, item)
98+
99+
Push *item* on the heap, then pop and return the largest item from the
100+
*heap*. The combined action runs more efficiently than :func:`heappush_max`
101+
followed by a separate call to :func:`heappop_max`.
102+
103+
104+
.. function:: heapify_max(x)
105+
106+
Transform list *x* into a max heap, in-place, in linear time.
107+
108+
109+
.. function:: heapreplace(heap, item)
110+
111+
Pop and return the smallest item from the *heap*, and also push the new *item*.
112+
The heap size doesn't change. If the heap is empty, :exc:`IndexError` is raised.
113+
114+
This one step operation is more efficient than a :func:`heappop` followed by
115+
:func:`heappush` and can be more appropriate when using a fixed-size heap.
116+
The pop/push combination always returns an element from the heap and replaces
117+
it with *item*.
118+
119+
The value returned may be larger than the *item* added. If that isn't
120+
desired, consider using :func:`heappushpop` instead. Its push/pop
121+
combination returns the smaller of the two values, leaving the larger value
122+
on the heap.
123+
124+
85125
The module also offers three general purpose functions based on heaps.
86126

87127

Lib/heapq.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def heapify(x):
178178
for i in reversed(range(n//2)):
179179
_siftup(x, i)
180180

181-
def _heappop_max(heap):
181+
def heappop_max(heap):
182182
"""Maxheap version of a heappop."""
183183
lastelt = heap.pop() # raises appropriate IndexError if heap is empty
184184
if heap:
@@ -188,14 +188,26 @@ def _heappop_max(heap):
188188
return returnitem
189189
return lastelt
190190

191-
def _heapreplace_max(heap, item):
191+
def heapreplace_max(heap, item):
192192
"""Maxheap version of a heappop followed by a heappush."""
193193
returnitem = heap[0] # raises appropriate IndexError if heap is empty
194194
heap[0] = item
195195
_siftup_max(heap, 0)
196196
return returnitem
197197

198-
def _heapify_max(x):
198+
def heappush_max(heap, item):
199+
"""Maxheap version of a heappush."""
200+
heap.append(item)
201+
_siftdown_max(heap, 0, len(heap)-1)
202+
203+
def heappushpop_max(heap, item):
204+
"""Maxheap fast version of a heappush followed by a heappop."""
205+
if heap and heap[0] < item:
206+
item, heap[0] = heap[0], item
207+
_siftup_max(heap, 0)
208+
return item
209+
210+
def heapify_max(x):
199211
"""Transform list into a maxheap, in-place, in O(len(x)) time."""
200212
n = len(x)
201213
for i in reversed(range(n//2)):
@@ -335,9 +347,9 @@ def merge(*iterables, key=None, reverse=False):
335347
h_append = h.append
336348

337349
if reverse:
338-
_heapify = _heapify_max
339-
_heappop = _heappop_max
340-
_heapreplace = _heapreplace_max
350+
_heapify = heapify_max
351+
_heappop = heappop_max
352+
_heapreplace = heapreplace_max
341353
direction = -1
342354
else:
343355
_heapify = heapify
@@ -490,10 +502,10 @@ def nsmallest(n, iterable, key=None):
490502
result = [(elem, i) for i, elem in zip(range(n), it)]
491503
if not result:
492504
return result
493-
_heapify_max(result)
505+
heapify_max(result)
494506
top = result[0][0]
495507
order = n
496-
_heapreplace = _heapreplace_max
508+
_heapreplace = heapreplace_max
497509
for elem in it:
498510
if elem < top:
499511
_heapreplace(result, (elem, order))
@@ -507,10 +519,10 @@ def nsmallest(n, iterable, key=None):
507519
result = [(key(elem), i, elem) for i, elem in zip(range(n), it)]
508520
if not result:
509521
return result
510-
_heapify_max(result)
522+
heapify_max(result)
511523
top = result[0][0]
512524
order = n
513-
_heapreplace = _heapreplace_max
525+
_heapreplace = heapreplace_max
514526
for elem in it:
515527
k = key(elem)
516528
if k < top:
@@ -584,15 +596,15 @@ def nlargest(n, iterable, key=None):
584596
except ImportError:
585597
pass
586598
try:
587-
from _heapq import _heapreplace_max
599+
from _heapq import heapreplace_max
588600
except ImportError:
589601
pass
590602
try:
591-
from _heapq import _heapify_max
603+
from _heapq import heapify_max
592604
except ImportError:
593605
pass
594606
try:
595-
from _heapq import _heappop_max
607+
from _heapq import heappop_max
596608
except ImportError:
597609
pass
598610

Lib/test/test_heapq.py

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# _heapq.nlargest/nsmallest are saved in heapq._nlargest/_smallest when
1515
# _heapq is imported, so check them there
1616
func_names = ['heapify', 'heappop', 'heappush', 'heappushpop', 'heapreplace',
17-
'_heappop_max', '_heapreplace_max', '_heapify_max']
17+
'heappop_max', 'heapreplace_max', 'heapify_max']
1818

1919
class TestModules(TestCase):
2020
def test_py_functions(self):
@@ -23,7 +23,7 @@ def test_py_functions(self):
2323

2424
@skipUnless(c_heapq, 'requires _heapq')
2525
def test_c_functions(self):
26-
for fname in func_names:
26+
for fname in ['heapify', 'heappop', 'heappush', 'heappushpop', 'heapreplace']:
2727
self.assertEqual(getattr(c_heapq, fname).__module__, '_heapq')
2828

2929

@@ -74,13 +74,47 @@ def test_push_pop(self):
7474
except AttributeError:
7575
pass
7676

77+
def test_max_push_pop(self):
78+
# 1) Push 256 random numbers and pop them off, verifying all's OK.
79+
heap = []
80+
data = []
81+
self.check_max_invariant(heap)
82+
for i in range(256):
83+
item = random.random()
84+
data.append(item)
85+
self.module.heappush_max(heap, item)
86+
self.check_max_invariant(heap)
87+
results = []
88+
while heap:
89+
item = self.module.heappop_max(heap)
90+
self.check_max_invariant(heap)
91+
results.append(item)
92+
data_sorted = data[:]
93+
data_sorted.sort(reverse=True)
94+
95+
self.assertEqual(data_sorted, results)
96+
# 2) Check that the invariant holds for a sorted array
97+
self.check_max_invariant(results)
98+
99+
self.assertRaises(TypeError, self.module.heappush, [])
100+
try:
101+
self.assertRaises(TypeError, self.module.heappush, None, None)
102+
self.assertRaises(TypeError, self.module.heappop, None)
103+
except AttributeError:
104+
pass
105+
77106
def check_invariant(self, heap):
78107
# Check the heap invariant.
79108
for pos, item in enumerate(heap):
80109
if pos: # pos 0 has no parent
81110
parentpos = (pos-1) >> 1
82111
self.assertTrue(heap[parentpos] <= item)
83112

113+
def check_max_invariant(self, heap):
114+
for pos in range(1, len(heap)):
115+
parentpos = (pos - 1) >> 1
116+
self.assertTrue(heap[parentpos] >= heap[pos])
117+
84118
def test_heapify(self):
85119
for size in list(range(30)) + [20000]:
86120
heap = [random.random() for dummy in range(size)]
@@ -89,6 +123,14 @@ def test_heapify(self):
89123

90124
self.assertRaises(TypeError, self.module.heapify, None)
91125

126+
def test_heapify_max(self):
127+
for size in list(range(30)) + [20000]:
128+
heap = [random.random() for dummy in range(size)]
129+
self.module.heapify_max(heap)
130+
self.check_max_invariant(heap)
131+
132+
self.assertRaises(TypeError, self.module.heapify, None)
133+
92134
def test_naive_nbest(self):
93135
data = [random.randrange(2000) for i in range(1000)]
94136
heap = []
@@ -153,12 +195,31 @@ def test_heappushpop(self):
153195
x = self.module.heappushpop(h, 11)
154196
self.assertEqual((h, x), ([11], 10))
155197

198+
def test_heappushpop_max(self):
199+
h = []
200+
x = self.module.heappushpop_max(h, 10)
201+
self.assertEqual((h, x), ([], 10))
202+
203+
h = [10]
204+
x = self.module.heappushpop_max(h, 10.0)
205+
self.assertEqual((h, x), ([10], 10.0))
206+
self.assertEqual(type(h[0]), int)
207+
self.assertEqual(type(x), float)
208+
209+
h = [10]
210+
x = self.module.heappushpop_max(h, 11)
211+
self.assertEqual((h, x), ([11], 10))
212+
213+
h = [10]
214+
x = self.module.heappushpop_max(h, 9)
215+
self.assertEqual((h, x), ([10], 9))
216+
156217
def test_heappop_max(self):
157-
# _heapop_max has an optimization for one-item lists which isn't
218+
# heapop_max has an optimization for one-item lists which isn't
158219
# covered in other tests, so test that case explicitly here
159220
h = [3, 2]
160-
self.assertEqual(self.module._heappop_max(h), 3)
161-
self.assertEqual(self.module._heappop_max(h), 2)
221+
self.assertEqual(self.module.heappop_max(h), 3)
222+
self.assertEqual(self.module.heappop_max(h), 2)
162223

163224
def test_heapsort(self):
164225
# Exercise everything with repeated heapsort checks
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make max heap functions public.

0 commit comments

Comments
 (0)