So, ive been instructed to create a function with 2 parameters, a list and a number, that uses a binary recursive search to see if a number is in a list. If the number is in the list i'm to return its index and if its not I am to return -1. So far i have
def findIndex(alist,num):
print(alist)
if len(alist) % 2 == 0:
mid = int((len(alist)/2)-1)
else:
mid = ((len(alist)//2))
if alist[mid] == num:
print(mid)
elif alist[mid] > num:
findIndex(alist[0:mid],num)
elif alist[mid] < num:
findIndex(alist[mid+1:],num)
I know how a binary search works. Do to the middle, if its not the number you're searching for compare that number to the number you're searching for. If its greater than the number youre searching for, search the front half of the list. If its lesser, search the back half of the list again. The problem is my code only works in the case that the number I'm searching for is less than the middle number in every case.
ANALYSIS
There are several problems with the logic.
The deleted post nailed your most glaring problem: your search works only when the search target appears in the middle of a series of left-only divisions. Otherwise, you print 0, the index when the list gets down to a single item.
If the target is not in the list, your program crashes on index out of range, when you try to find the midpoint of an empty list.
You never return anything. Printing a result is not the same as returning a value.
SOLUTION
There are two straightforward ways to handle this. The first is to use findIndex as a wrapper function, and write the function you want to be called by that. For instance:
def findIndex(alist,num):
return binaryFind(alist, 0, len(alist), num)
def binaryFind(alist, left, right, target):
# Here, you write a typical binary search function
# with left & right limits.
The second is to return the index you find, but adjust it for all of the times you cut off the left half of the list. Each level of call has to add that adjustment to the return value, passing the sum back to the previous level. The simple case looks like this, where you're recurring on the right half of the list:
elif alist[mid] < num:
return (mid+1) + findIndex(alist[mid+1:], num)
Does that get you moving toward a useful solution?
Related
My code is meant to find the longest path in a matrix, where each value is greater than the one previous. However, I've been instructed to not use for loops at all, which is difficult because I have 3, with 2 of them being involved in a nested loop. Is there any way I could only user recursion to solve this?
def path(self, matrix):
res = 1
# for loop to run the function for every element in list
for row in range (len(matrix)):
for col in range (len(matrix[0])):
# pass in the current max and the new spot, and take the max value
res = max(res, self.dfs(matrix, row, col))
# return the max value
return res
# function to compare paths (Depth-First Seach)
def dfs(self, matrix, row, col):
# if spot was visited before, return value from cache
if (row, col) in self.cache:
return self.cache[(row, col)]
# Set a default value of 1
self.cache[(row, col)] = 1
# moving the tile of focus
for rowVal, colVal in self.directions:
newRow = row + rowVal
newCol = col + colVal
# if the pointer can move in a direction (not out of bounds), and is greater: store cache value
if (0 <= newRow < len(matrix)) and (0 <= newCol < len(matrix[0])) and matrix[row][col] < matrix[newRow][newCol]:
self.cache[(row, col)] = max(self.cache[(row, col)], 1 + self.dfs(matrix, newRow, newCol))
Recursion is about a base case and an iterative case. In your situation, think of the smallest matrix you can - an empty matrix. That is your base case. Your function should return a path length of 0 if it is empty.
The iterative case is a bit more difficult, and usually where things become confusing. The key goal of the iterative case is to reduce the size of the problem, usually by the smallest amount possible.
To overly simplify, if you start with a function like this:
def f(ls):
for x in ls:
result = g(x, result)
return result
Then the iterative version looks like this:
def f(ls, result):
if 0 == len(x): # Base Case
return result
else: # Iterative Case
result = g(x, result)
return f(ls[1:], result)
The trick is figuring out what of your internal logic goes into g() and how to represent the result.
Let's take a simpler version of your problem, where we deal with a single array and want to return the longest 'path' in that array. A path is a sequence of integers that are incrementing by one.
So, if we have [0,1,2,3] the expected value is 4. If we have [0,1,1,2,3] the expected value is 3. Similarly, 3 would be expected for the input [0,1,2,2,1]. [3,2,1] should return 0.
The most basic signature we require is this:
def f(ls: list[int]) -> int
Essentially, 'a function that takes a list of ints and returns the length of the longest path'. But we have to remember a bit of extra state to do this properly. Specifically, we need to remember the length of the current path we are on, and the lengths of all paths we have found.
def f(ls: list[int], current_path: int) -> int
Let's examine the base case. A base case is any case where reducing the size of your input (in our case 'input' really refers only to ls) would not yield an easier problem to solve. There are two base cases - if the input list is length 0 or 1. In both these cases, we have no need to shrink the problem any further.
def f(ls: list[int], current_path: int) -> int:
# If there are no elements, current_path is the only valid length
if 0 == len(ls):
return current_path
# If there is one element, increment current_path before returning it, to account for the length that element adds
if 1 == len(ls):
return current_path + 1
These serve to terminate the recursion. Now lets look at the iterative case:
def f(ls: list[int], current_path: int) -> int:
# Base cases
if 0 == len(ls):
return current_path
if 1 == len(ls):
return current_path + 1
# Iterative case - guaranteed that len(ls) >= 2
current_path = current_path + 1 # Increment the current_path to account for the current element.
if ls[1] == ls[0] + 1:
# In this branch we know that the path will continue.
return f(ls[1:], current_path) # ls[1:] reduces our problem space by one element
else:
# In this branch the path ends, because the next element breaks the incremental sequence.
recursive_result = returnf(ls[1:], 0) # Reduce the problem space and start a new path of length 0
return max(recursive_result, current_path) # Choose which path is longer
There is a lot going on here, so I'll break it down in parts. The key thing to remember is that we are going to reduce the problem space - shorten the list - and then recurse with that smaller problem space. The element we are removing is therefore key to determining how we proceed.
Since we are removing one element, we add one to the current path. If the incoming path length was 0, we now have a path length of 1. If it was 3, we now have a path of 4.
Then we check the value of the next element. Is it exactly larger than the current element? If so, we know the path will continue, so we recurse, passing along a list without the current element and the length of our current path.
If it is not exactly one more, we know our current path ends here. In the recursion we pass along a new path length of 0, resetting the counter. But we have to do something with the returned value - decide whether it is larger than the current path as it stands at this element. Hence using max() to choose between the two possibilities.
This gives you a recursive function that iteratively shortens the problem at each step until it finds a base case, at which point it returns - but it returns up through the recursive function, accumulating the results.
(n.b. There are ways to optimize this, clean it up, add default values, etc. I'm skipping that because it doesn't help you think about the recursion.)
Your actual problem is harder. You're going along a two dimensional array. The key insight to have is this: in the iterative step in the example I gave, I looked at all the possible cases for moving forward and chose between them. However, you can go down all possible paths. If you are at a particular element in a two dimensional array, you know that you can go one way or the other - that's two recursive function calls. Because recursion is shortening your problem space, your iterative step can simply trust it will return, and only deal with the results. In your case, that is choosing which of the two recursive calls you made returned the larger result.
(At this point I have to make assumptions about your problem because you included neither a complete specification nor full code.)
def f(matrix: list[list[int], coords: (int, int), current_path: int) -> int:
# Find all possible 'next steps'. For a next step to be valid it must be exactly one greater than the current location.
# Base Case - There are no possible next steps -> return current path + 1
# Increment path
# Iterative cases
# There is only one next step -> recurse passing new coordinates and path length
# There are two or three next steps -> recurse passing new coordinates and path length, then choose which result is the longest.
The difficulty here is that this finds the longest path from any given starting position. To truly find the longest path in the matrix, you would have to add a fourth argument to your function - a list of all the starting positions you have tried. Then you would change your logic for finding the next steps from 'is it strictly one larger' to 'is it strictly one larger or have I not tried starting from that point'?
# Use type aliases so you're clear about the types
type Matrix: list[list[int]]
type Coordinate: (int, int) # x-y coordinates
type Cache: list[Coordinate] # All the places we've started from
def f(matrix: Matrix,
coords: Coordinate,
current_path: int,
starting_points: Cache) -> int:
if 0 == len(matrix):
return current_path
if 1 == len(matrix) and 0 == len(matrix[0]):
return current_path
current_path = current_path + 1 # From here on, we have a valid element at this coordinate
if 1 == len(matrix) and 1 == len(matrix[0]):
return current_Path
moves = get_all_moves(...)
if 0 == len(moves): # This is *also* a base case - the problem cannot be shrunk any further!
return current_path
results = try_each_move(moves) # This is also a recursive function... but a *different* recursive function, in order to avoid using a for loop (or list comprehension)
return max(max(results), current_path)
A few closing notes:
Do try to adhere to python style guides. It makes reading python easier!
Any information you think you need to store outside the function can just be passed as a parameter. Pure recursive functions don't have access to a closure or outer scope. Depending on your case, this may not be the most efficient solution, but it is where you should start until you're much more comfortable with recursion.
Similarly, if copying a value rather than a reference makes it easier for your to reason about what you're doing, do that first. Efficiency is for later. Clarity first.
Recursion often (though not always) easier if you're building up a solution from the return of the recursion call. In the examples here, the max() function is doing that accretion, but you could imagine inverting the approach here and first doing the recursive call, which returns two values - the value of the last element and the length of the path. Then you could decide if you're smaller than that value. I didn't do that here because you'd have to remember two path lengths at a time.
In this specific problem, do take care with the cache. You can't just remember if you've ever visited a coordinate.
Recursion is just a function that keeps calling itself until it reaches a condition that disallows it from continuing.
Here's an example that's themed toward your needs.
""" Emulation Of:
for row in range (len(matrix)):
for col in range (len(matrix[0])):
print(matrix[row][col])
"""
matrix = [[1,2,3],[4,5,6],[7,8,9]]
#wrapping everything in this function makes `i` unreachable
#because it should be managed internally
def process_matrix(r:int, c:int) -> None:
def columns(i:int=0, r:int=0) -> None:
if i==c: return #columns finished
print(matrix[r][i]) #work
columns(i+1, r) #next column
def rows(i:int=0) -> None:
if i==r: return #rows finished
columns(0, i) #recurse all columns for this row
rows(i+1) #next row
rows(0) #start recursion
#use
process_matrix(len(matrix), len(matrix[0]))
If you are trying to retrieve data, you have to return the "recursion call". Otherwise, you'll get None back from the very first call, and the recursion will carry on in a way that is unreachable by your code.
data = [10,20,30,40,50,60]
def where_is_50(i:int=0) -> int:
if data[i] == 50:
return i #stop recursion
return where_is_50(i+1) #next
print(where_is_50())
If it isn't clear, The first time the function is called, it is not 50 so, it returns a call to itself. However, the actual return can't finish until the call does. Essentially, you end up with a string of "active" functions that are all waiting for the call that finds 50. When 50 is found, the return value keeps ascending through all the calls back to the very first one.
Whatever recursive functions you make should have a local reference to the data to traverse. In other words, don't pass your entire matrix on each call. Pass names or indexes recursively.
First time posting here, so apologies in advance if I am not following best practices. My algorithm is supposed to do the following in a sorted array with possible duplicates.
Return -1 if the element does not exist in the array
Return the smallest index where the element is present.
I have written a binary search algorithm for an array without duplicate. This returns a position of the element or -1. Based on blackbox testing, I know that the non-duplicate version of the binary search works. I have then recursively called that function via another function to search from 0 to position-1 to find the first incidence of the element, if any.
I am currently failing a black box test. I am getting a wrong answer error and not a time out error. I have tried most of the corner cases that I could think of and also ran a brute force test with the naive search algorithm and could not find an issue.
I am looking for some guidance on what might be wrong in the implementation rather than an alternate solution.
The format is as follow:
Input:
5 #array size
3 4 7 7 8 #array elements need to be sorted
5 #search query array size
3 7 2 8 4 #query elements
Output
0 2 -1 4 1
My code is shown below:
class BinarySearch:
def __init__(self,input_list,query):
self.array=input_list
self.length=len(input_list)
self.query=query
return
def binary_search(self,low,high):
'''
Implementing the binary search algorithm with distinct numbers on a
sorted input.
'''
#trivial case
if (self.query<self.array[low]) or (self.query>self.array[high-1]):
return -1
elif (low>=high-1) and self.array[low]!=self.query:
return -1
else:
m=low+int(np.floor((high-low)/2))
if self.array[low]==self.query:
return low
elif (self.array[m-1]>=self.query):
return self.binary_search(low,m)
elif self.array[high-1]==self.query:
return high-1
else:
return self.binary_search(m,high)
return
class DuplicateBinarySearch(BinarySearch):
def __init__(self,input_list,query):
BinarySearch.__init__(self,input_list,query)
def handle_duplicate(self,position):
'''
Function handles the duplicate number problem.
Input: position where query is identified.
Output: updated earlier position if it exists else return
original position.
'''
if position==-1:
return -1
elif position==0:
return 0
elif self.array[position-1]!=self.query:
return position
else:
new_position=self.binary_search(0,position)
if new_position==-1 or new_position>=position:
return position
else:
return self.handle_duplicate(new_position)
def naive_duplicate(self,position):
old_position=position
if position==-1:
return -1
else:
while position>=0 and self.array[position]==self.query:
position-=1
if position==-1:
return old_position
else:
return position+1
if __name__ == '__main__':
num_keys = int(input())
input_keys = list(map(int, input().split()))
assert len(input_keys) == num_keys
num_queries = int(input())
input_queries = list(map(int, input().split()))
assert len(input_queries) == num_queries
for q in input_queries:
item=DuplicateBinarySearch(input_keys,q)
#res=item.handle_duplicate(item.binary_search(0,item.length))
#res=item.naive_duplicate(item.binary_search(0,item.length))
#assert res_check==res
print(item.handle_duplicate(item.binary_search(0,item.length)), end=' ')
#print(item.naive_duplicate(item.binary_search(0,item.length)), end=' ')
When I run a naive duplicate algorithm, I get a time out error:
Failed case #56/57: time limit exceeded (Time used: 10.00/5.00, memory used: 42201088/536870912.)
When I run the binary search with duplicate algorithm, I get a wrong answer error on a different test case:
Failed case #24/57: Wrong answer
(Time used: 0.11/5.00, memory used: 42106880/536870912.)
The problem statement is as follows:
Problem Statement
Update:
I could make the code work by making the following change but I have not been able to create a test case to see why the code would fail in the first case.
Original binary search function that works with no duplicates but fails an unknown edge case when a handle_duplicate function calls it recursively. I changed the binary search function to the following:
def binary_search(self,low,high):
'''
Implementing the binary search algorithm with distinct numbers on a sorted input.
'''
#trivial case
if (low>=high-1) and self.array[low]!=self.query:
return -1
elif (self.query<self.array[low]) or (self.query>self.array[high-1]):
return -1
else:
m=low+(high-low)//2
if self.array[low]==self.query:
return low
elif (self.array[m-1]>=self.query):
return self.binary_search(low,m)
elif self.array[m]<=self.query:
return self.binary_search(m,high)
elif self.array[high-1]==self.query:
return high-1
else:
return -1
Since you are going to implement binary search with recursive, i would suggest you add a variable 'result' which act as returning value and hold intermediate index which equal to target value.
Here is an example:
def binarySearchRecursive(nums, left, right, target, result):
"""
This is your exit point.
If the target is not found, result will be -1 since it won't change from initial value.
If the target is found, result will be the index of the first occurrence of the target.
"""
if left > right:
return result
# Overflow prevention
mid = left + (right - left) // 2
if nums[mid] == target:
# We are not sure if this is the first occurrence of the target.
# So we will store the index to the result now, and keep checking.
result = mid
# Since we are looking for "first occurrence", we discard right half.
return binarySearchRecursive(nums, left, mid - 1, target, result)
elif target < nums[mid]:
return binarySearchRecursive(nums, left, mid - 1, target, result)
else:
return binarySearchRecursive(nums, mid + 1, right, target, result)
if __name__ == '__main__':
nums = [2,4,4,4,7,7,9]
target = 4
(left, right) = (0, len(nums)-1)
result = -1 # Initial value
index = binarySearchRecursive(nums, left, right, target, result)
if index != -1:
print(index)
else:
print('Not found')
From your updated version, I still feel the exit point of your function is a little unintuitive.(Your "trivial case" section)
Since the only condition that your searching should stop, is that you have searched all possible section of the list. That is when the range of searching area is 0, there is no element left to be search and check. In implementation, that is when left < right, or high < low, is true.
The 'result' variable, is initialized as -1 when the function first been called from main. And won't change if there is no match find. And after each successful matching, since we can not be sure if it is the first occurrence, we will just store this index into the result. If there are more 'left matching', then the value will be update. If there is not, then the value will be eventually returned. If the target is not in the list, the return will be -1, as its original initialized value.
Regarding calculation of the list mid-point: why is there
i = (first +last) //2
and last is initialized to len(a_list) - 1? From my quick tests, this algorithm without -1 works correctly.
def binary_search(a_list, item):
"""Performs iterative binary search to find the position of an integer in a given, sorted, list.
a_list -- sorted list of integers
item -- integer you are searching for the position of
"""
first = 0
last = len(a_list) - 1
while first <= last:
i = (first + last) / 2
if a_list[i] == item:
return '{item} found at position {i}'.format(item=item, i=i)
elif a_list[i] > item:
last = i - 1
elif a_list[i] < item:
first = i + 1
else:
return '{item} not found in the list'.format(item=item)
The last legal index is len(a_list) - 1. The algorithm will work correctly, as first will always be no more than this, so that the truncated mean will never go out of bounds. However, without the -1, the midpoint computation will be one larger than optimum about half the time, resulting in a slight loss of speed.
Consider the case where the item you're searching for is greater than all the elements of the list. In that case the statement first = i + 1 gets executed repeatedly. Finally you get to the last iteration of the loop, where first == last. In that case i is also equal to last, but if last=len() then i is off the end of the list! The first if statement will fail with an index out of range.
See for yourself: https://ideone.com/yvdTzo
You have another error in that code too, but I'll let you find it for yourself.
Here's my python code
def search(myList, number):
for i in myList:
if i[0] == number:
return i[1]
return None
myList = [(5107261, 'Ernst'), (6524256, 'Arvo')]
number = 5107261
print(search(myList, number))
Now I want to write it using recursion but I'm not sure how to do it. I need some pointers to help me get started.
When writing recursive code, you want to define a base case, and you want to define a method for making your problem smaller on every step. In this example, we are working with lists, so a good base case would be an empty list, []. If the list is empty, it makes sense to return None. In your recursive case, you want to do some work to make the problem smaller. In this case we can check one element, and if that element is not what we are searching for, we can call the function again on a smaller version of the list.
Our result is a function like this:
def searchR(myList, number):
if length(myList) == 0: return None
elif myList[0][0] == number: return myList[0][1]
else: return searchR(myList[1:], number)
There are 3 cases. Case 1 is our base case, where the length of the list is 0. Case 2 is our success case, where we found the the target of the search. Case 3 is where we make our recursive call. Notice how the first element is removed from the new list! If the first element isn't removed, the function will loop forever.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm trying to implement a Binary Search algorithm in Python.
I wrote this code on my phone but it didn't compile.
And I don't know why it doesn't compile.
(I haven't tried it on a computer.)
def BinarySearch(sList, search):
res=0
sortedList=sorted(sList)
x=int(len(sortedList)/2)
mid=sortedList[x]
def DivideSearch(sortedList ,search ):
first=sortedList[:mid ]
second=sortedList[mid:]
if first[len(first)-1]<search:
DivideSearch (first, search)
elif second[len(second)-1]>search:
DivideSearch (second, search)
elif len(first)==1:
res=first.pop()
elif len(second)==1:
res=second.pop()
if res==search:
return res
numbers=[1,2,3,4,5,6,7,8,9]
guess=3
print(BinarySearch(numbers,guess ))
What keeps this code from compiling?
What are my mistakes and how can I fix them?
First, your code is running fine on my machine. Second, your logic is flawed. res never gets assigned in the BinarySearch() function because it is in a different scope than in the parent function. Also, your base case check should not be done on first or second it should be done on sortedList at the beginning of the function. Also, you can do your checking if the value was found in the DivideSearch() function. I'm uploading corrected code, take a look at this
import random
def DivideSearch(sortedList, search, mid):
first = sortedList[:mid]
second = sortedList[mid:]
#check for our base case
if len(sortedList) ==1:
return sortedList[0] if sortedList[0] == search else None
#we can immediately remove half the cases if they're less than or greater than our search value
#if the greatest value element in the lower half is < search value, only search the higher value list
if first[-1] < search:
#recurse
return DivideSearch(second, search, len(second)/2)
#otherwise we want to check the lower value list
else:
return DivideSearch(first, search, len(first)/2)
def BinarySearch(sList, search):
sortedList=sorted(sList)
#determine mid cleanup
mid=len(sortedList)/2
#return the result
return DivideSearch(sortedList,search, mid)
numbers=[random.randint(1, 10) for x in range(1,10)]
guess=5
def binarys(list, item):
#you have to keep track of the head(lo) of the list and tail(hi)
lo = 0
#a list must be sorted for binary search to work because the lower values are on the left and higher on the right
slist = sorted(list)
hi = len(slist) - 1
#Keep running the search as long as the start of the list is never less than or equal to the end of the list. At that point you either have 1 item left to check or the item isn't there at all. So return False
while lo <= hi:
mid = (lo + hi)//2
#if the item you searched for is in the middle, return True
if slist[mid] == item:
return True
#since it's not in the middle the first time you checked, but if the item you're looking for is less than the value of the mid item in the list then you can ignore the entire right part of the list by making the item to the left of the midpoint the new tail(hi). midpoint minus 1 because you already established the midpoint of the original list didn't have the item you searched for.
elif item < slist[mid]:
hi = mid - 1
# if the item you're looking for is greater than the value of the mid item in the list then you can ignore the entire left part of the list by making the item to the right of the midpoint the new head(lo). midpoint plus 1 because you already established the midpoint of the original list didn't have the item you searched for.
else:
if item > slist[mid]:
lo = mid+ 1
return False
print(binarys([1,2,3,4,5,6,7,8,9,10], 1))