1
1
#!/usr/bin/env python3
2
2
3
3
"""
4
- This is pure Python implementation of binary search algorithms
4
+ Pure Python implementations of binary search algorithms
5
5
6
- For doctests run following command:
6
+ For doctests run the following command:
7
7
python3 -m doctest -v binary_search.py
8
8
9
9
For manual testing run:
@@ -34,16 +34,12 @@ def bisect_left(
34
34
Examples:
35
35
>>> bisect_left([0, 5, 7, 10, 15], 0)
36
36
0
37
-
38
37
>>> bisect_left([0, 5, 7, 10, 15], 6)
39
38
2
40
-
41
39
>>> bisect_left([0, 5, 7, 10, 15], 20)
42
40
5
43
-
44
41
>>> bisect_left([0, 5, 7, 10, 15], 15, 1, 3)
45
42
3
46
-
47
43
>>> bisect_left([0, 5, 7, 10, 15], 6, 2)
48
44
2
49
45
"""
@@ -79,16 +75,12 @@ def bisect_right(
79
75
Examples:
80
76
>>> bisect_right([0, 5, 7, 10, 15], 0)
81
77
1
82
-
83
78
>>> bisect_right([0, 5, 7, 10, 15], 15)
84
79
5
85
-
86
80
>>> bisect_right([0, 5, 7, 10, 15], 6)
87
81
2
88
-
89
82
>>> bisect_right([0, 5, 7, 10, 15], 15, 1, 3)
90
83
3
91
-
92
84
>>> bisect_right([0, 5, 7, 10, 15], 6, 2)
93
85
2
94
86
"""
@@ -124,7 +116,6 @@ def insort_left(
124
116
>>> insort_left(sorted_collection, 6)
125
117
>>> sorted_collection
126
118
[0, 5, 6, 7, 10, 15]
127
-
128
119
>>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)]
129
120
>>> item = (5, 5)
130
121
>>> insort_left(sorted_collection, item)
@@ -134,12 +125,10 @@ def insort_left(
134
125
True
135
126
>>> item is sorted_collection[2]
136
127
False
137
-
138
128
>>> sorted_collection = [0, 5, 7, 10, 15]
139
129
>>> insort_left(sorted_collection, 20)
140
130
>>> sorted_collection
141
131
[0, 5, 7, 10, 15, 20]
142
-
143
132
>>> sorted_collection = [0, 5, 7, 10, 15]
144
133
>>> insort_left(sorted_collection, 15, 1, 3)
145
134
>>> sorted_collection
@@ -167,7 +156,6 @@ def insort_right(
167
156
>>> insort_right(sorted_collection, 6)
168
157
>>> sorted_collection
169
158
[0, 5, 6, 7, 10, 15]
170
-
171
159
>>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)]
172
160
>>> item = (5, 5)
173
161
>>> insort_right(sorted_collection, item)
@@ -177,12 +165,10 @@ def insort_right(
177
165
False
178
166
>>> item is sorted_collection[2]
179
167
True
180
-
181
168
>>> sorted_collection = [0, 5, 7, 10, 15]
182
169
>>> insort_right(sorted_collection, 20)
183
170
>>> sorted_collection
184
171
[0, 5, 7, 10, 15, 20]
185
-
186
172
>>> sorted_collection = [0, 5, 7, 10, 15]
187
173
>>> insort_right(sorted_collection, 15, 1, 3)
188
174
>>> sorted_collection
@@ -191,29 +177,28 @@ def insort_right(
191
177
sorted_collection .insert (bisect_right (sorted_collection , item , lo , hi ), item )
192
178
193
179
194
- def binary_search (sorted_collection : list [int ], item : int ) -> int | None :
195
- """Pure implementation of binary search algorithm in Python
180
+ def binary_search (sorted_collection : list [int ], item : int ) -> int :
181
+ """Pure implementation of a binary search algorithm in Python
196
182
197
- Be careful collection must be ascending sorted, otherwise result will be
183
+ Be careful collection must be ascending sorted otherwise, the result will be
198
184
unpredictable
199
185
200
186
:param sorted_collection: some ascending sorted collection with comparable items
201
187
:param item: item value to search
202
- :return: index of found item or None if item is not found
188
+ :return: index of the found item or -1 if the item is not found
203
189
204
190
Examples:
205
191
>>> binary_search([0, 5, 7, 10, 15], 0)
206
192
0
207
-
208
193
>>> binary_search([0, 5, 7, 10, 15], 15)
209
194
4
210
-
211
195
>>> binary_search([0, 5, 7, 10, 15], 5)
212
196
1
213
-
214
197
>>> binary_search([0, 5, 7, 10, 15], 6)
215
-
198
+ -1
216
199
"""
200
+ if list (sorted_collection ) != sorted (sorted_collection ):
201
+ raise ValueError ("sorted_collection must be sorted in ascending order" )
217
202
left = 0
218
203
right = len (sorted_collection ) - 1
219
204
@@ -226,66 +211,66 @@ def binary_search(sorted_collection: list[int], item: int) -> int | None:
226
211
right = midpoint - 1
227
212
else :
228
213
left = midpoint + 1
229
- return None
214
+ return - 1
230
215
231
216
232
- def binary_search_std_lib (sorted_collection : list [int ], item : int ) -> int | None :
233
- """Pure implementation of binary search algorithm in Python using stdlib
217
+ def binary_search_std_lib (sorted_collection : list [int ], item : int ) -> int :
218
+ """Pure implementation of a binary search algorithm in Python using stdlib
234
219
235
- Be careful collection must be ascending sorted, otherwise result will be
220
+ Be careful collection must be ascending sorted otherwise, the result will be
236
221
unpredictable
237
222
238
223
:param sorted_collection: some ascending sorted collection with comparable items
239
224
:param item: item value to search
240
- :return: index of found item or None if item is not found
225
+ :return: index of the found item or -1 if the item is not found
241
226
242
227
Examples:
243
228
>>> binary_search_std_lib([0, 5, 7, 10, 15], 0)
244
229
0
245
-
246
230
>>> binary_search_std_lib([0, 5, 7, 10, 15], 15)
247
231
4
248
-
249
232
>>> binary_search_std_lib([0, 5, 7, 10, 15], 5)
250
233
1
251
-
252
234
>>> binary_search_std_lib([0, 5, 7, 10, 15], 6)
253
-
235
+ -1
254
236
"""
237
+ if list (sorted_collection ) != sorted (sorted_collection ):
238
+ raise ValueError ("sorted_collection must be sorted in ascending order" )
255
239
index = bisect .bisect_left (sorted_collection , item )
256
240
if index != len (sorted_collection ) and sorted_collection [index ] == item :
257
241
return index
258
- return None
242
+ return - 1
259
243
260
244
261
245
def binary_search_by_recursion (
262
- sorted_collection : list [int ], item : int , left : int , right : int
263
- ) -> int | None :
264
- """Pure implementation of binary search algorithm in Python by recursion
246
+ sorted_collection : list [int ], item : int , left : int = 0 , right : int = - 1
247
+ ) -> int :
248
+ """Pure implementation of a binary search algorithm in Python by recursion
265
249
266
- Be careful collection must be ascending sorted, otherwise result will be
250
+ Be careful collection must be ascending sorted otherwise, the result will be
267
251
unpredictable
268
252
First recursion should be started with left=0 and right=(len(sorted_collection)-1)
269
253
270
254
:param sorted_collection: some ascending sorted collection with comparable items
271
255
:param item: item value to search
272
- :return: index of found item or None if item is not found
256
+ :return: index of the found item or -1 if the item is not found
273
257
274
258
Examples:
275
259
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4)
276
260
0
277
-
278
261
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4)
279
262
4
280
-
281
263
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4)
282
264
1
283
-
284
265
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4)
285
-
266
+ -1
286
267
"""
268
+ if right < 0 :
269
+ right = len (sorted_collection ) - 1
270
+ if list (sorted_collection ) != sorted (sorted_collection ):
271
+ raise ValueError ("sorted_collection must be sorted in ascending order" )
287
272
if right < left :
288
- return None
273
+ return - 1
289
274
290
275
midpoint = left + (right - left ) // 2
291
276
@@ -297,12 +282,78 @@ def binary_search_by_recursion(
297
282
return binary_search_by_recursion (sorted_collection , item , midpoint + 1 , right )
298
283
299
284
285
+ def exponential_search (sorted_collection : list [int ], item : int ) -> int :
286
+ """Pure implementation of an exponential search algorithm in Python
287
+ Resources used:
288
+ https://en.wikipedia.org/wiki/Exponential_search
289
+
290
+ Be careful collection must be ascending sorted otherwise, result will be
291
+ unpredictable
292
+
293
+ :param sorted_collection: some ascending sorted collection with comparable items
294
+ :param item: item value to search
295
+ :return: index of the found item or -1 if the item is not found
296
+
297
+ the order of this algorithm is O(lg I) where I is index position of item if exist
298
+
299
+ Examples:
300
+ >>> exponential_search([0, 5, 7, 10, 15], 0)
301
+ 0
302
+ >>> exponential_search([0, 5, 7, 10, 15], 15)
303
+ 4
304
+ >>> exponential_search([0, 5, 7, 10, 15], 5)
305
+ 1
306
+ >>> exponential_search([0, 5, 7, 10, 15], 6)
307
+ -1
308
+ """
309
+ if list (sorted_collection ) != sorted (sorted_collection ):
310
+ raise ValueError ("sorted_collection must be sorted in ascending order" )
311
+ bound = 1
312
+ while bound < len (sorted_collection ) and sorted_collection [bound ] < item :
313
+ bound *= 2
314
+ left = bound // 2
315
+ right = min (bound , len (sorted_collection ) - 1 )
316
+ last_result = binary_search_by_recursion (
317
+ sorted_collection = sorted_collection , item = item , left = left , right = right
318
+ )
319
+ if last_result is None :
320
+ return - 1
321
+ return last_result
322
+
323
+
324
+ searches = ( # Fastest to slowest...
325
+ binary_search_std_lib ,
326
+ binary_search ,
327
+ exponential_search ,
328
+ binary_search_by_recursion ,
329
+ )
330
+
331
+
300
332
if __name__ == "__main__" :
301
- user_input = input ("Enter numbers separated by comma:\n " ).strip ()
333
+ import doctest
334
+ import timeit
335
+
336
+ doctest .testmod ()
337
+ for search in searches :
338
+ name = f"{ search .__name__ :>26} "
339
+ print (f"{ name } : { search ([0 , 5 , 7 , 10 , 15 ], 10 ) = } " ) # type: ignore[operator]
340
+
341
+ print ("\n Benchmarks..." )
342
+ setup = "collection = range(1000)"
343
+ for search in searches :
344
+ name = search .__name__
345
+ print (
346
+ f"{ name :>26} :" ,
347
+ timeit .timeit (
348
+ f"{ name } (collection, 500)" , setup = setup , number = 5_000 , globals = globals ()
349
+ ),
350
+ )
351
+
352
+ user_input = input ("\n Enter numbers separated by comma: " ).strip ()
302
353
collection = sorted (int (item ) for item in user_input .split ("," ))
303
- target = int (input ("Enter a single number to be found in the list:\n " ))
304
- result = binary_search (collection , target )
305
- if result is None :
354
+ target = int (input ("Enter a single number to be found in the list: " ))
355
+ result = binary_search (sorted_collection = collection , item = target )
356
+ if result == - 1 :
306
357
print (f"{ target } was not found in { collection } ." )
307
358
else :
308
- print (f"{ target } was found at position { result } in { collection } ." )
359
+ print (f"{ target } was found at position { result } of { collection } ." )
0 commit comments