Recursion binary search in Python [duplicate] - python

This question already has answers here:
What is the difference between '/' and '//' when used for division?
(16 answers)
Closed 7 months ago.
I have a list with numbers from 0-9:
mylist = list(range(10))
I am getting an error with the division command to get mid:
def binary_search(mylist, element, low, high):
low=0
high= len(mylist)
mid=low + (high- mymin)/2
if mid==len(mylist):
return False
elif mylist[mid]==element:
return mid
elif high==low:
return False
elif mylist[mid]<element:
return binary_search(mylist, element, mymin, mid-1)
elif mylist[mid]<element:
return binary_search(mylist, element, mid+1, mymax)
else:
return mid
and if I wanted to return True how would I write that on top of return binary_search(mylist, element, mymin, mid-1)?

Recursive solution:
def binary_search_recursive(arr, elem, start=0, end=None):
if end is None:
end = len(arr) - 1
if start > end:
return False
mid = (start + end) // 2
if elem == arr[mid]:
return mid
if elem < arr[mid]:
return binary_search_recursive(arr, elem, start, mid-1)
# elem > arr[mid]
return binary_search_recursive(arr, elem, mid+1, end)
Iterative solution:
def binary_search_iterative(arr, elem):
start, end = 0, (len(arr) - 1)
while start <= end:
mid = (start + end) // 2
if elem == arr[mid]:
return mid
if elem < arr[mid]:
end = mid - 1
else: # elem > arr[mid]
start = mid + 1
return False

Here's an elegant recursive solution if you're:
1) Trying to return the INDEX of the target in the original list being passed in, before it is halved. Getting the target is the easy part.
2) Only want to have to pass in 2 arguments: (list, target) instead of 4 arguments including the upper/lower (right/left) bounds of each array being passed in recursively.
3) Don't want out of bounds, maximum recursion depth, or target not found errors.
# Base Case: one item (target) in array.
# Recursive Case: cut array by half each recursive call.
def recursive_binary_search(arr, target):
mid = len(arr) // 2
if len(arr) == 1:
return mid if arr[mid] == target else None
elif arr[mid] == target:
return mid
else:
if arr[mid] < target:
callback_response = recursive_binary_search((arr[mid:]), target)
return mid + callback_response if callback_response is not None else None
else:
return recursive_binary_search((arr[:mid]), target)

please correct me, if the code presents any bugs
def binary_recursive(array, val):
if val < array[0] or val > array[-1]:
return False
mid = len(array) // 2
left = array[:mid]
right = array[mid:]
if val == array[mid]:
return True
elif array[mid] > val:
return binary_recursive(left, val)
elif array[mid] < val:
return binary_recursive(right, val)
else:
return False

`Please correct me if I am wrong as I am a new programmer but here is my solution:
def binary_search(test_cases, item_to_be_found):
try:
if item_to_be_found == test_cases[len(test_cases) // 2]:
return True
elif item_to_be_found < test_cases[len(test_cases) // 2]:
test_cases = test_cases[:len(test_cases) // 2]
else:
test_cases = test_cases[len(test_cases) // 2:]
return binary_search(test_cases, item_to_be_found)
except RecursionError:
return False

The first solution looks wrong because it doesn't index the list.
This problem tripped me up too the first time I wrote a solution so be sure to test your algorithm well.
Here's what I ended up with:
def binary_search(value, items, low=0, high=None):
"""
Binary search function.
Assumes 'items' is a sorted list.
The search range is [low, high)
"""
high = len(items) if high is None else high
pos = low + (high - low) / len(items)
if pos == len(items):
return False
elif items[pos] == value:
return pos
elif high == low:
return False
elif items[pos] < value:
return binary_search(value, items, pos + 1, high)
else:
assert items[pos] > value
return binary_search(value, items, low, pos)
And when I test it, the answers look correct:
In [9]: for val in range(7):
...: print val, binary_search(val, [1, 2, 3, 5])
...:
0 False
1 0
2 1
3 2
4 False
5 3
6 False
Btw, Python has a library module for just this kind of thing named bisect.

Though it's too late, it might help someone else :-
def bsearch_helper(arr, key, low, high):
if low > high:
return False
mid = (low + high)//2
if arr[mid] == key:
return True
elif arr[mid] < key:
return bsearch_helper(arr, key, mid + 1, high)
else:
return bsearch_helper(arr, key, low, mid - 1)
return False
def bsearch(arr, key):
return bsearch_helper(arr, key, 0, len(arr) - 1)
if __name__ == '__main__':
arr = [8, 3, 9, 2, 6, 5, 1, 7, 4]
print (bsearch(sorted(arr), 5))

You can make use of list slicing too.
def binary_search_recursive(list1, element):
if len(list1) == 0:
return False
else:
mid = len(list1)//2
if (element == list1[mid]):
return True
else:
if element > list1[mid]:
return binary_search_recursive(list1[mid+1:],element)
else:
return binary_search_recursive(list1[:mid],element)
However, note that list slicing introduces additional complexity.

There are a lot of solutions here already. Below is one more solution without slicing and that just requires element and list as arguments:
def binary_search(item, arr):
def _binary_search(item, first, last, arr):
if last < first:
return False
if last == first:
return arr[last] == item
mid = (first + last) // 2
if arr[mid] > item:
last = mid
return _binary_search(item, first, last, arr)
elif arr[mid] < item:
first = mid + 1
return _binary_search(item, first, last, arr)
else:
return arr[mid] == item
return _binary_search(item, 0, len(arr) - 1, arr)
print(binary_search(-1, [0, 1, 2, 3, 4, 5]))

Recursion Binary Search for sorted list.
The print statements are helpful to see how it all works.
# n = number we are searching for
# lst = the sorted list we are searching in
# sp = list start position
# ep = list end postion
def binary_search_recursion(n: int, lst: list, sp: int = 0, ep: int = None) -> bool:
# first time searching, start position will be 0
# and end position will be None
if ep is None:
# End position equals total length minus 1 since 0 indexed
ep = len(lst) - 1
# get the midpoint of the list (lst)
mid = (sp + ep) // 2
mid_item = lst[mid]
print(f"Start: lst[{sp}] = {lst[sp]}\nMid: lst[{mid}] = {mid_item}\nEnd: lst[{ep}] = {lst[ep]}")
# If mid item matches the searching number then success
if mid_item == n:
print(f"Success!!! Number {n} found in lst[{mid}]")
return True
# Else if mid item is greater than number, we eliminate everything to the left and move right
elif mid_item > n:
new_ep = mid - 1
if new_ep < 0:
print(f"Number {n} is too low. Lowest number found is {lst[sp]}")
return False
else:
print(f"Number {n} is less than mid item {mid_item}, change ep {ep} to {new_ep}.\n")
binary_search_recursion(n, lst, sp, new_ep)
# Else if mid item is lower than number, we eliminate everything to the right and move left
elif mid_item < n:
new_sp = mid + 1
if new_sp > ep:
print(f"Number {n} is too High. Highest number found is {lst[ep]}")
return False
else:
print(f"Number {n} is greater than mid item {mid_item}, change sp {sp} to {new_sp}.\n")
binary_search_recursion(n, lst, new_sp, ep)
Testing out the function:
# A sorted list
num_list = [10,20,30,40,50,60,70,80,90,100,110]
# Search for 10 in num_list
binary_search_recursion(10, num_list)
Start: lst[0] = 10
Mid: lst[5] = 60
End: lst[10] = 110
Number 10 is less than mid item 60, change ep 10 to 4.
Start: lst[0] = 10
Mid: lst[2] = 30
End: lst[4] = 50
Number 10 is less than mid item 30, change ep 4 to 1.
Start: lst[0] = 10
Mid: lst[0] = 10
End: lst[1] = 20
Success!!! Number 10 found in lst[0]
# Search for 110 in num_list
binary_search_recursion(110, num_list)
Start: lst[0] = 10
Mid: lst[5] = 60
End: lst[10] = 110
Number 110 is greater than mid item 60, change sp 0 to 6.
Start: lst[6] = 70
Mid: lst[8] = 90
End: lst[10] = 110
Number 110 is greater than mid item 90, change sp 6 to 9.
Start: lst[9] = 100
Mid: lst[9] = 100
End: lst[10] = 110
Number 110 is greater than mid item 100, change sp 9 to 10.
Start: lst[10] = 110
Mid: lst[10] = 110
End: lst[10] = 110
Success!!! Number 110 found in lst[10]
# Search for 6 in num_list
binary_search_recursion(6, num_list)
Start: lst[0] = 10
Mid: lst[5] = 60
End: lst[10] = 110
Number 6 is less than mid item 60, change ep 10 to 4.
Start: lst[0] = 10
Mid: lst[2] = 30
End: lst[4] = 50
Number 6 is less than mid item 30, change ep 4 to 1.
Start: lst[0] = 10
Mid: lst[0] = 10
End: lst[1] = 20
Number 6 is too low. Lowest number found is 10
# Search for 1111 in num_list
binary_search_recursion(1111, num_list)
Start: lst[0] = 10
Mid: lst[5] = 60
End: lst[10] = 110
Number 1111 is greater than mid item 60, change sp 0 to 6.
Start: lst[6] = 70
Mid: lst[8] = 90
End: lst[10] = 110
Number 1111 is greater than mid item 90, change sp 6 to 9.
Start: lst[9] = 100
Mid: lst[9] = 100
End: lst[10] = 110
Number 1111 is greater than mid item 100, change sp 9 to 10.
Start: lst[10] = 110
Mid: lst[10] = 110
End: lst[10] = 110
Number 1111 is too High. Highest number found is 110

Your first one won't even get started, because list(mid) will immediately raise a TypeError: 'list' object is not callable.
If you fix that (by using list[mid]), your next problem is that you ignore the min and max arguments you receive, and instead set them to 0 and len(list)-1 each time. So, you will infinitely recurse (until you eventually get a RecursionError for hitting the stack limit).
Think about it: the first call to binarySearch(l, 5, 0, 10) recursively calls binarySearch(l, 5, 0, 4). But that call ignores that 4 and acts as if you'd passed 10, so it recursively calls binarySearch(l, 5, 0, 4). Which calls binarySearch(l, 5, 0, 4). And so on.
If you fix that (by removing those two lines), you've got another problem. When you give it the number 10, binarySearch(l, 10, 0, 10) will call binarySearch(l, 10, 6, 10), which will callbinarySearch(l, 10, 8, 10), then binarySearch(l, 10, 9, 10), thenbinarySearch(l, 10, 10, 10). And that last one will check list[10] > element. But list[10] is going to raise an IndexError, because there aren't 11 elements in the list.
And once you fix that off-by-one error, there are no problems left. The problem you asked about cannot possibly ever occur. Here's an example run:
>>> a = range(10)
>>> for i in -3, -1, 0, 1, 4, 5, 9, 10, 11:
... print i, binarySearch(a, i, 0, 10)
-3 False
-1 False
0 0
1 1
4 4
5 5
9 9
10 False
11 False
Your second version isn't recursive. bSearch never calls bSearch, and that's the whole definition of recursion.
There's nothing wrong with writing an iterative algorithm instead of a recursive algorithm (unless you're doing a homework problem and recursion is the whole point), but your function isn't iterative either—there are no loops anywhere.
(This version also ignores the start and end arguments, but that's not as much of a problem in this case, because, again, you're not doing any recursion.)
Anyway, the only return False in the function is in that first if len(list) == 0. So, for any non-empty list, it's either going to return True, or a number. With your input, it will return 4 for anything less than the value at the midpoint of the list (5), and True for anything else.

Your problem here is that you're redeclaring min and max in each loop, so although it should be recursive, passing in a new min or max each time, this isn't in fact happening.
You can solve this by using defaults in the arguments:
def binary_search(list, element, min=0, max=None):
max = max or len(list)-1
if max<min:
return False
else:
mid= min + (max-min)/2
if mid>element:
return binary_search(list, element, min, mid-1)
elif mid<element:
return binary_search(list, element, mid+1, max)
else:
return mid
If you're not familiar with the syntax on line 2,
max = max or len(list)-1
max will be set to len(list)-1 only if max is not passed in to the method.
So you can call the method simply using:
binary_search(range(10), 7)
# Returns 7
binary_search(range(10), 11)
# Returns False

Just another answer to the same question:
def binary_search(array, element, mini=0, maxi=None):
"""recursive binary search."""
maxi = len(array) - 1 if maxi is None else maxi
if mini == maxi:
return array[mini] == element
else:
median = (maxi + mini)/2
if array[median] == element:
return True
elif array[median] > element:
return binary_search(array, element, mini, median)
else:
return binary_search(array, element, median+1, maxi)
print binary_search([1,2,3],2)

I made this one. Correct me if there's any bug.
import math
def insert_rec(A,v,fi,li):
mi = int(math.floor((li+fi)/2))
if A[mi] == v:
print("Yes found at: ",mi)
return
if fi==li or fi>li:
print("Not found")
return
if A[mi] < v:
fi = mi+1
insert_rec(A,v,fi,li)
if A[mi] > v:
li = mi-1
insert_rec(A,v,fi,li)

def bs(list,num): #presume that the list is a sorted list
#base case
mid=int(len(list)/2) # to divide the list into two parts
if num==list[mid]:
return True
if len(list)==1:
return False
#recursion
elif num<list[mid]: #if the num is less than mid
return bs(list[0:mid],num) #then omit all the nums after the mid
elif num>list[mid]: #if the num is greater than mid
return bs(list[mid:],num) # then omit all the nums before the mid
#return False
list=[1,2,3,4,5,6,7,8,9,10]
print(bs(list,20))
<<< False
print(bs(list,4))
<<< True

You can implement binary search in python in the following way.
def binary_search_recursive(list_of_numbers, number, start=0, end=None):
# The end of our search is initialized to None. First we set the end to the length of the sequence.
if end is None:
end = len(list_of_numbers) - 1
if start > end:
# This will happen if the list is empty of the number is not found in the list.
raise ValueError('Number not in list')
mid = (start + end) // 2 # This is the mid value of our binary search.
if number == list_of_numbers[mid]:
# We have found the number in our list. Let's return the index.
return mid
if number < list_of_numbers[mid]:
# Number lies in the lower half. So we call the function again changing the end value to 'mid - 1' Here we are entering the recursive mode.
return binary_search_recursive(list_of_numbers, number, start, mid - 1)
# number > list_of_numbers[mid]
# Number lies in the upper half. So we call the function again changing the start value to 'mid + 1' Here we are entering the recursive mode.
return binary_search_recursive(list_of_numbers, number, mid + 1, end)
We should check our code with good unittest to find loop holes in our code.
Hope this helps you.

Here is My Recursion Solution of Binary Search
def recBinarySearch(arr,ele):
if len(arr) == 0:
return False
else:
mid = len(arr)/2
if arr[mid] == ele:
return True
else:
if ele < arr[mid]:
return recBinarySearch(arr[:mid], ele)
else:
return recBinarySearch(arr[mid+1], ele)

You can do it this way as well:
def recursive_binary_search(list, item):
"""find the index of the given item in the list recursively"""
low = 0
high = len(list) - 1
if low <= high:
mid = low + high
guess = list[mid]
if guess == item:
return mid
if guess > item: # item must be in the first split
return recursive_binary_search(list[low:mid], item)
else: # check in second split otherwise
return recursive_binary_search(list[mid:high], item)
return None
def main():
print(recursive_binary_search([2,3,4,5,6,7,8], 3)) # will print 1
if __name__ == "__main__":
main()

Related

Design O(log n) algorithm for finding 3 distinct elements in a list

The question is:
Design an O(log n) algorithm whose input is a sorted list A. The algorithm should return true if A contains at least 3 distinct elements. Otherwise, the algorithm should return false.
as it has to be O(log n), I tried to use binary search and this is the code I wrote:
def hasThreeDistinctElements(A):
if len(A) < 3:
return False
minInd = 0
maxInd = len(A)-1
midInd = (maxInd+minInd)//2
count = 1
while minInd < maxInd:
if A[minInd] == A[midInd]:
minInd = midInd
if A[maxInd] == A[midInd]:
maxInd = midInd
else:
count += 1
maxInd -= 1
else:
count += 1
minInd += 1
midInd = (maxInd+minInd)//2
return count >= 3
is there a better way to do this?
Thanks
from bisect import bisect
def hasThreeDistinctElements(A):
return A[:1] < A[-1:] > [A[bisect(A, A[0])]]
The first comparison safely(*) checks whether there are two different values at all. If so, we check whether the first value larger than A[0] is also smaller than A[-1].
(*): Doesn't crash if A is empty.
Or without bisect, binary-searching for a third value in A[1:-1]. The invariant is that if there is any, it must be in A[lo : hi+1]:
def hasThreeDistinctElements(A):
lo, hi = 1, len(A) - 2
while lo <= hi:
mid = (lo + hi) // 2
if A[mid] == A[0]:
lo = mid + 1
elif A[mid] == A[-1]:
hi = mid - 1
else:
return True
return False
In order to really be O(logN), the updates to the bounding indeces minInd,maxInd should only ever be
maxInd = midInd [- 1]
minInd = midInd [+ 1]
to half the search space. Since there are paths through your loop body that only do
minInd += 1
maxInd -= 1
respectively, I am not sure that you can't create data for which your function is linear. The following is a bit simpler and guaranteed O(logN)
def x(A):
if len(A) < 3:
return False
minInd, maxInd = 0, len(A)-1
mn, mx = A[minInd], A[maxInd]
while minInd < maxInd:
midInd = (minInd + maxInd) // 2
if mn != A[midInd] != mx:
return True
if A[midInd] == mn:
minInd = midInd + 1 # minInd == midInd might occur
else:
maxInd = midInd # while maxInd != midInd is safe
return False
BTW, if you can use the standard library, it is as easy as:
from bisect import bisect_right
def x(A):
return A and (i := bisect_right(A, A[0])) < len(A) and A[i] < A[-1]
Yes, there is a better approach.
As the list is sorted, you can use binary search with slight custom modifications as follows:
list = [1, 1, 1, 2, 2]
uniqueElementSet = set([])
def binary_search(minIndex, maxIndex, n):
if(len(uniqueElementSet)>=3):
return
#Checking the bounds for index:
if(minIndex<0 or minIndex>=n or maxIndex<0 or maxIndex>=n):
return
if(minIndex > maxIndex):
return
if(minIndex == maxIndex):
uniqueElementSet.add(list[minIndex])
return
if(list[minIndex] == list[maxIndex]):
uniqueElementSet.add(list[minIndex])
return
uniqueElementSet.add(list[minIndex])
uniqueElementSet.add(list[maxIndex])
midIndex = (minIndex + maxIndex)//2
binary_search(minIndex+1, midIndex, n)
binary_search(midIndex+1, maxIndex-1, n)
return
binary_search(0, len(list)-1, len(list))
print(True if len(uniqueElementSet)>=3 else False)
As, we are dividing the array into 2 parts in each iteration of the recursion, it will require maximum of log(n) steps to check if it contains 3 unique elements.
Time Complexity = O(log(n)).

How do you assign a return value to a variable in python? [duplicate]

This question already has answers here:
Why does my recursive function return None?
(4 answers)
Closed 2 years ago.
I've been working on binary insertion sort and I came across a problem.It keeps telling me that
"NoneType' object cannot be interpreted as an integer" at the "binary_insertion_sort" function before the second for loop. Can anyone explain what's wrong with my code and tell me if there's any logic error in it?
def binary_search(n, bin_list, low, high):
print(low,high)
if high - low <= 1:
if n < bin_list[low]:
return low - 1
elif n > bin_list[high]:
print('d',n,high)
return high + 1
elif n == bin_list[low]:
return low
elif n== bin_list[high]:
return high
else:
print('c',low)
return low+1
mid = (low+high)//2
print('a',mid)
if n < bin_list[mid]:
binary_search(n, bin_list, 0, mid-1)
elif n > bin_list[mid]:
binary_search(n, bin_list, mid+1, high)
else:
return mid
the binary_insertion_sort part
def binary_insertion_sort(text):
sorted_list = text.split()
for num in range(1, len(sorted_list)):
unsorted = sorted_list[num]
print(sorted_list)
index = binary_search(unsorted, sorted_list, 0, len(sorted_list)-1)
for j in range(num, index, -1):
print(j)
sorted_list[j] = sorted_list[j-1]
sorted_list[index-1] = num
return sorted_list
a = binary_insertion_sort('1 2 3 4 5')
I think you are missing the returns for the recursive cases in the binary_search function.
As the function enters one of the first two cases in the last if:
if n < bin_list[mid]:
binary_search(n, bin_list, 0, mid-1)
elif n > bin_list[mid]:
binary_search(n, bin_list, mid+1, high)
else:
return mid
You are assigning None (the function return type) to the index var. You should try using:
if n < bin_list[mid]:
return binary_search(n, bin_list, 0, mid-1)
elif n > bin_list[mid]:
return binary_search(n, bin_list, mid+1, high)
else:
return mid

Why is my Sum algorithm stuck in a loop/Taking a very long time?

I'm new to the world of algorithms and have attempted to write my own after studying a few. I'm trying to find if the sum of two numbers in an array sum up to a target value. The issue is that it loops continuously and I would be extremely grateful for some help as i just cant seem to see what it is.
arr = [1,2,3,4,4]
target = 8
def findSum(arr, target):
if len(arr) <= 1:
return False
low = 0
high = len(arr) - 1
while low <= high:
for i in range(low, high):
for j in range(high, low, -1):
if (arr[i]+arr[j]) == target:
return(arr[i], arr[j])
elif (arr[i]+arr[j]) > target:
high -= 1
else:
low += 1
return False
findSum(arr, target)
P.S Sorry about the uncommented code, I didn't think it was necessary with the simplicity of it.
You are doing it incorrectly,
You might want to try this,
arr = [1,2,3,4,4]
target = 8
def findSum(arr, target):
start = 0
end = len(arr) - 1
while start < end:
if arr[start] + arr[end] == target:
return (arr[start], arr[end])
elif arr[start] + arr[end] < target:
start += 1
else:
end -= 1
return False
print(findSum(arr, target))
OUTPUT:
(4, 4)
If what you're after is any two elements that sum up to the target value, this is much simpler version:
arr = [1,2,3,4,4]
target = 8
def findSum(arr, target):
for i in range(len(arr)):
for j in range(len(arr)):
if (arr[i]+arr[j]) == target and i != j:
return(arr[i], arr[j])
return False
print(findSum(arr, target))
Problem with your code:
This is your outer loop
for i in range(low, high):
This is your inner loop
for j in range(high, low, -1):
While the outer loop is still at i==0, the inner loop has run len(arr) times, increasing the low from 0 to 4.
Then it will stuck there. Because low == high == 4, it will not leave the while loop, nor run the range.

Recursive binary search algorithm doesn't stop executing after condition is met, returns a nonetype object

I have made a binary search algorithm, biSearch(A, high, low, key). It takes in a sorted array and a key, and spits out the position of key in the array. High and low are the min and max of the search range.
It almost works, save for one problem:
On the second "iteration" (not sure what the recursive equivalent of that is), a condition is met and the algorithm should stop running and return "index". I commented where this happens. Instead, what ends up happening is that the code continues on to the next condition, even though the preceding condition is true. The correct result, 5, is then overridden and the new result is a nonetype object.
within my code, I have commented in caps the problems at the location in which they occur. Help is much appreciated, and I thank you in advance!
"""
Created on Sat Dec 28 18:40:06 2019
"""
def biSearch(A, key, low = False, high = False):
if low == False:
low = 0
if high == False:
high = len(A)-1
if high == low:
return A[low]
mid = low + int((high -low)/ 2)
# if key == A[mid] : two cases
if key == A[mid] and high - low == 0: #case 1: key is in the last pos. SHOULD STOP RUNNING HERE
index = mid
return index
elif key == A[mid] and (high - low) > 0:
if A[mid] == A[mid + 1] and A[mid]==A[mid -1]: #case 2: key isnt last and might be repeated
i = mid -1
while A[i] == A[i+1]:
i +=1
index = list(range(mid- 1, i+1))
elif A[mid] == A[mid + 1]:
i = mid
while A[i]== A[i+1]:
i += 1
index = list(range(mid, i+1))
elif A[mid] == A[mid -1]:
i = mid -1
while A[i] == A[i +1]:
i += 1
index = list(range(mid, i +1))
elif key > A[mid] and high - low > 0: # BUT CODE EXECTUES THIS LINE EVEN THOUGH PRECEDING IS ALREADY MET
index = biSearch(A, key, mid+1, high)
elif key < A[mid] and high - low > 0:
index = biSearch(A, key, low, mid -1)
return index
elif A[mid] != key: # if key DNE in A
return -1
#biSearch([1,3,5, 4, 7, 7,7,9], 1, 8, 7)
#x = biSearch([1,3,5, 4, 7,9], 1, 6, 9)
x = biSearch([1,3,5, 4, 7,9],9)
print(x)
# x = search([1,3,5, 4, 7,9], 9)
This function is not a binary search. Binary search's time complexity should be O(log(n)) and works on pre-sorted lists, but the complexity of this algorithm is at least O(n log(n)) because it sorts its input parameter list for every recursive call. Even without the sorting, there are linear statements like list(range(mid, i +1)) on each call, making the complexity quadratic. You'd be better off with a linear search using list#index.
The function mutates its input parameter, which no search function should do (we want to search, not search and sort).
Efficiencies and mutation aside, the logic is difficult to parse and is overkill in any circumstance. Not all nested conditionals lead to a return, so it's possible to return None by default.
You can use the builtin bisect module:
>>> from bisect import *
>>> bisect_left([1,2,2,2,2,3,4,4,4,4,5], 2)
1
>>> bisect_left([1,2,2,2,2,3,4,4,4,4,5], 4)
6
>>> bisect_right([1,2,2,2,2,3,4,4,4,4,5], 4)
10
>>> bisect_right([1,2,2,2,2,3,4,4,4,4,5], 2)
5
>>> bisect_right([1,2,2,2,2,3,4,4,4,4,5], 15)
11
>>> bisect_right([1,2,5,6], 3)
2
If you have to write this by hand as an exercise, start by looking at bisect_left's source code:
def bisect_left(a, x, lo=0, hi=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e < x, and all e in
a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
insert just before the leftmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
# Use __lt__ to match the logic in list.sort() and in heapq
if a[mid] < x: lo = mid+1
else: hi = mid
This is easy to implement recursively (if desired) and then test against the builtin:
def bisect_left(a, target, lo=0, hi=None):
if hi is None: hi = len(a)
mid = (hi + lo) // 2
if lo >= hi:
return mid
elif a[mid] < target:
return bisect_left(a, target, mid + 1, hi)
return bisect_left(a, target, lo, mid)
if __name__ == "__main__":
from bisect import bisect_left as builtin_bisect_left
from random import choice, randint
from sys import exit
for _ in range(10000):
a = sorted(randint(0, 100) for _ in range(100))
if any(bisect_left(a, x) != builtin_bisect_left(a, x) for x in range(-1, 101)):
print("fail")
exit(1)
Logically, for any call frame, there's only 3 possibilities:
The lo and hi pointers have crossed, in which case we've either found the element or figured out where it should be if it were in the list; either way, return the midpoint.
The element at the midpoint is less than the target, which guarantees that the target is in the tail half of the search space, if it exists.
The element at the midpoint matches or is less than the target, which guarantees that the target is in the front half of the search space.
Python doesn't overflow integers, so you can use the simplified midpoint test.

Binary search in a Python list

I am trying to perform a binary search on a list in python. List is created using command line arguments. User inputs the number he wants to look for in the array and he is returned the index of the element. For some reason, the program only outputs 1 and None. Code is below.
Any help is extremely appreciated.
import sys
def search(list, target):
min = 0
max = len(list)-1
avg = (min+max)/2
while (min < max):
if (list[avg] == target):
return avg
elif (list[avg] < target):
return search(list[avg+1:], target)
else:
return search(list[:avg-1], target)
print "The location of the number in the array is", avg
# The command line argument will create a list of strings
# This list cannot be used for numeric comparisions
# This list has to be converted into a list of ints
def main():
number = input("Please enter a number you want to search in the array !")
index = int(number)
list = []
for x in sys.argv[1:]:
list.append(int(x))
print "The list to search from", list
print(search(list, index))
if __name__ == '__main__':
main()
CL :
Anuvrats-MacBook-Air:Python anuvrattiku$ python binary_search.py 1 3 4 6 8 9 12 14 16 17 27 33 45 51 53 63 69 70
Please enter a number you want to search in the array !69
The list to search from [1, 3, 4, 6, 8, 9, 12, 14, 16, 17, 27, 33, 45, 51, 53, 63, 69, 70]
0
Anuvrats-MacBook-Air:Python anuvrattiku$
In Python2 and Python3 you can use bisect as written in the comments.
Replace your search with the following
from bisect import bisect_left
def search(alist, item):
'Locate the leftmost value exactly equal to item'
i = bisect_left(alist, item)
if i != len(alist) and alist[i] == item:
return i
raise ValueError
alist = [1,2,7,8,234,5,9,45,65,34,23,12]
x = 5
alist.sort() # bisect only works on sorted lists
print(search(alist, x)) # prints 2 as 5 is on position 2 in the sorted list
Also, the AS SortedCollection (Python recipe) could be useful.
The following code (from here) performs the binary search and returns position and if the item was found at all.
def binarySearch(alist, item):
first = 0
last = len(alist)-1
found = False
while first<=last and not found:
pos = 0
midpoint = (first + last)//2
if alist[midpoint] == item:
pos = midpoint
found = True
else:
if item < alist[midpoint]:
last = midpoint-1
else:
first = midpoint+1
return (pos, found)
Will return (2, True) if used in the example above.
Well, there are some little mistakes in your code. To find them, you should either use a debugger, or at least add traces to understand what happens. Here is your original code with traces that make the problems self evident:
def search(list, target):
min = 0
max = len(list)-1
avg = (min+max)/2
print list, target, avg
...
You can immediately see that:
you search in a sub array that skips avg-1 when you are below avg
as you search in a sub array you will get the index in that subarray
The fixes are now trivial:
elif (list[avg] < target):
return avg + 1 + search(list[avg+1:], target) # add the offset
else:
return search(list[:avg], target) # sublist ends below the upper limit
That's not all, when you end the loop with min == max, you do not return anything (meaning you return None). And last but not least never use a name from the standard Python library for your own variables.
So here is the fixed code:
def search(lst, target):
min = 0
max = len(lst)-1
avg = (min+max)/2
# uncomment next line for traces
# print lst, target, avg
while (min < max):
if (lst[avg] == target):
return avg
elif (lst[avg] < target):
return avg + 1 + search(lst[avg+1:], target)
else:
return search(lst[:avg], target)
# avg may be a partial offset so no need to print it here
# print "The location of the number in the array is", avg
return avg
Recursive:
def in_list(l, x):
if len(l) < 2:
if l[0] == x:
return True
else:
return False
mid = len(l) // 2
if x < l[mid]:
return in_list(l[:mid], x)
else:
return in_list(l[mid:], x)
or iterative:
def in_list2(l, x):
low = 0
high = len(l) - 1
while low <= high:
mid = (low + high) // 2
if l[mid] == x:
return True
if x < l[mid]:
high = mid - 1
else:
low = mid + 1
return False
#Serge Ballesta 's solution is undoubtly the correct answer to this question.
I am just going to add another way of solving this:
def search(arr, item, start, end):
if end-start == 1:
if arr[start] == item:
return start
else:
return -1;
halfWay = int( (end-start) / 2)
if arr[start+halfWay] > item:
return search(arr, item, start, end-halfWay)
else:
return search(arr, item, start+halfWay, end)
def binarysearch(arr, item):
return search(arr, item, 0, len(arr))
arr = [1, 3, 4, 6, 8, 9, 12, 14, 16, 17, 27, 33, 45, 51, 53, 63, 69, 70]
print("Index of 69: " + str(binarysearch(arr, 69))) # Outputs: 16
The reason you aren't getting correct result is because in every recursive call your code is sending sliced array. So the array length keeps reducing. Ideally you should work out a way to send original array and work with only start, end indices.

Categories

Resources