Binary Search in Python: correct slicing - python

Please, help me unterstand the silly question about how binary sort algorithm's works.
So, lets take input array of [4, 5, 6, 7, 8, 11, 20] where i'm searching for index of 6 value (see the code).
As I take it should work this way:
First of all we take the pivot(middle) point of (end-start)/2=7 as of index=3
After the first iteration we got array's slice of [4, 5, 6] where we searching the mid point again. With result of index=1 and value = 5.
After second iteration we get the only array of 6, which meets the basic condition and we getting correct result.
To prove my assumption I added an output of the cutted array I should get at the second and third step.
But surprisingly it was [4, 5] on the 2nd and [] on the third step in opposite to [4,5,6] and [6] I expected?
Relating to slicing documentation a[start:stop] # items start through stop-1, so the last one isn't included.
But how homes, I'm getting the correct result assuming i'm working with [4,5,6] and [6] in opposite to incorrect output?
The code is:
def binarySearch(arr, start, end, x):
print('the input array is: ' + str(arr[start:end]))
if end>=start:
mid_idx=(start+end)//2
print('mid index is: ' + str(mid_idx))
if arr[mid_idx]==x:
return mid_idx
elif arr[mid_idx]>x:
return binarySearch(arr,start,mid_idx-1, x)
else:
return binarySearch(arr,mid_idx+1,end, x)
else:
return None
arr=[4, 5, 6, 7, 8, 11, 20]
res=binarySearch(arr, 0, len(arr),6)
print(res)
The output is:
the input array is: [4, 5, 6, 7, 8, 11, 20]
mid index is: 3
the input array is: [4, 5]
mid index is: 1
the input array is: []
mid index is: 2
2

It is good that you are surprised, because the code is not correct. It mixes two ways to interpret the end argument. Some implementations will treat end as included in the range to consider, while others treat end as the index after the intended range (this way is how slices/ranges are defined in Python).
The mix in your code can be seen here:
The calling code assumes the end argument as after the range
Your debugging print assumes the end argument as after the range
The first ("left") recursive call assumes the end argument is inclusive
The if condition assumes the end argument is inclusive.
You obviously cannot mix the two approaches and must make changes to make it consistent.
So either (excluding):
def binarySearch(arr, start, end, x):
print('the input array is: ' + str(arr[start:end]))
if end>start:
mid_idx=(start+end)//2
print('mid index is: ' + str(mid_idx))
if arr[mid_idx]==x:
return mid_idx
elif arr[mid_idx]>x:
return binarySearch(arr,start,mid_idx, x)
else:
return binarySearch(arr,mid_idx+1,end, x)
else:
return None
arr=[4, 5, 6, 7, 8, 11, 20]
res=binarySearch(arr, 0, len(arr),6)
print(res)
Or (including):
def binarySearch(arr, start, end, x):
print('the input array is: ' + str(arr[start:end+1]))
if end>=start:
mid_idx=(start+end)//2
print('mid index is: ' + str(mid_idx))
if arr[mid_idx]==x:
return mid_idx
elif arr[mid_idx]>x:
return binarySearch(arr,start,mid_idx-1, x)
else:
return binarySearch(arr,mid_idx+1,end, x)
else:
return None
arr=[4, 5, 6, 7, 8, 11, 20]
res=binarySearch(arr, 0, len(arr)-1,6)
print(res)

Related

Trying to understand how this code I found for triangle max path sum in python works

So take a triangle formatted as a nested list.
e.g.
t = [[5],[3, 6],[8, 14, 7],[4, 9, 2, 0],[9, 11, 5, 2, 9],[1, 3, 8, 5, 3, 2]]
and define a path to be the sum of elements from each row of the triangle,
moving 1 to the left or right as you go down rows. Or in python
the second index either stays the same or we add 1 to it.
a_path = [t[0][0],[t[1][1]],t[2][1],t[3][1],t[4][2],t[5][3]] = [5, 6, 14, 9, 5,5] is valid
not_a_path = [t[0][0],[t[1][0]],t[2][2],t[3][1],t[4][0],t[5][4]] = [5, 3, 7, 9, 9, 3] is not valid
For a triangle as small as this example this can obviously be done via brute force.
I wrote a function like that, for a 20 row triangle it takes about 1 minuite.
I need a function that can do this for a 100 row triangle.
I found this code on https://rosettacode.org/wiki/Maximum_triangle_path_sum#zkl and it agrees with all the results my terrible function outputs for small triangles I've tried, and using %time in the console it can do the 100 line triangle in 0 ns so relatively quick.
def maxPathSum(rows):
return reduce(
lambda xs, ys: [
a + max(b, c) for (a, b, c) in zip(ys, xs, xs[1:])
],
reversed(rows[:-1]), rows[-1]
)
So I started taking bits of this, and using print statements and the console to work out what it was doing. I get that reversed(rows[:-1]), rows[-1] is reversing the triangle so that we can iterate from all possible final values on the last row through the sums of their possible paths to get to that value, and that as a,b,c iterate: a is a number from the bottom row, b is the second from bottom row, c is the third from bottom row. And as they iterate I think a + max(b,c) seems to sum a with the greatest number on b or c, but when I try to find the max of either two lists or a nested list in the console the list returned seems completely arbitrary.
ys = t[-1]
xs = list(reversed(t[:-1]))
for (a, b, c) in zip(ys, xs, xs[1:]):
print(b)
print(c)
print(max(b,c))
print("")
prints
[9, 11, 5, 2, 9]
[4, 9, 2, 0]
[9, 11, 5, 2, 9]
[4, 9, 2, 0]
[8, 14, 7]
[8, 14, 7]
[8, 14, 7]
[3, 6]
[8, 14, 7]
[3, 6]
[5]
[5]
If max(b,c) returned the list containing max(max(b),max(c)) then b = [3, 6], c = [5] would return b, so not that. If max(b,c) returned the list with the greatest sum, max(sum(b),sum(c)), then the same example contradicts it. It doesn't return the list containg minimum value or the one with the greatest mean, so my only guess is that the fact that I set xs = list(reversed(t[:-1])) is the problem and that it works fine if its an iterator inside the lambda function but not in console.
Also trying to find a + max (b,c) gives me this error, which makes sense.
TypeError: unsupported operand type(s) for +: 'int' and 'list'
My best guess is again that the different definition of xs as a list is the problem. If true I would like to know how this all works in the context of being iterators in the lambda function. I think I get what reduce() and zip() are doing, so mostly just the lambda function is what's confusing me.
Thanks in advance for any help
We can simplify the expression a bit by including all the rows in the second argument to reduce - there's no reason to pass the last row as third parameter (the starting value) of reduce.
Then, it really helps to give your variables meaningful names, which the original code badly fails to do.
So, this becomes:
from functools import reduce
def maxPathSum(rows):
return reduce(
lambda sums, upper_row: [cell + max(sum_left, sum_right)
for (cell, sum_left, sum_right)
in zip(upper_row, sums, sums[1:])],
reversed(rows)
)
On the first iteration, sums will be the last row, and upper_row the one over it.
The lambda will calculate the best possible sums by adding each value of the upper row with the largest value of sums to its left or right.
It zips the upper row with the sums (the last sum won't be used, as there is one too much), and the sums shifted by one value. So, zip will provide us with a triplet (value from upper row (cell), sum underneath to its left (sum_left), sum underneath to its right (sum_right). The best possible sum at this point is our current cell + the largest of theses sums.
The lambda returns this new row of sums, which will be used as the first parameter of reduce (sums) on the next iteration, while upper_row becomes the next row in reversed(rows).
In the end, reduce returns the last row of sums, which contains only one value, our best possible total:
[53]
you can spell out the lambda function so it can print. does this help you understand?
t = [[5],[3, 6],[8, 14, 7],[4, 9, 2, 0],[9, 11, 5, 2, 9],[1, 3, 8, 5, 3, 2]]
def g( xs, ys):
ans=[a + max(b, c) for (a, b, c) in zip(ys, xs, xs[1:])]
print(ans)
return ans
def maxPathSum(rows):
return reduce(
g,
reversed(rows[:-1]), rows[-1]
)
maxPathSum(t)

Change index value in generator expression python

i'm new to python and I'm trying to do a condition inside a generator expression, and if my condition is met, I want to divide the index by 2, and add the index to a list. Else, i want to multiply by 3 and add 1. Also the order needs to be from n to 1. I cant really figure out where I update the index.
My code so far as follows:
def foo(n):
print(list(i // 2 if i % 2 == 0 else i * 3 + 1 for i in reversed(range(n + 1))))
Assuming n=5, output should be:
[5, 16, 8, 4, 2, 1]
My output:
[16, 2, 10, 1, 4, 0]
I saw that you can iterate using something like:
for i in range(start, end, -1)
How do I change the " -1 " to match my condition? (i=i/2 in case of even number, i*3+1 else).
Or I'm completely off course in which case I would like a hint. Thanks!

Find 4 values in a list that are close together

I am trying to find the 4 closest value in a given list within a defined value for the difference. The list can be of any length and is sorted in increasing order. Below is what i have tried:
holdlist=[]
m=[]
nlist = []
t = 1
q = [2,3,5,6,7,8]
for i in range(len(q)-1):
for j in range(i+1,len(q)):
if abs(q[i]-q[j])<=1:
holdlist.append(i)
holdlist.append(j)
t=t+1
break
else:
if t != 4:
holdlist=[]
t=1
elif t == 4:
nlist = holdlist
holdlist=[]
t=1
nlist = list(dict.fromkeys(nlist))
for num in nlist:
m.append(q[num])
The defined difference value here is 1. Where "q" is the list and i am trying to get the result in "m" to be [5,6,7,8]. but it turns out to be an empty list.
This works only if the list "q" is [5,6,7,8,10,11]. My guess is after comparing the last value, the for loop ends and the result does not go into "holdlist".
Is there a more elegant way of writing the code?
Thank you.
One solution would be to sort the input list and find the smallest window of four elements. Given the example input, this is
min([sorted(q)[i:i+4] for i in range(len(q) - 3)],
key=lambda w: w[3] - w[0])
But given a different input this will still return a value if the smallest window has a bigger spacing than 1. But I'd still use this solution, with a bit of error handling:
assert len(q) > 4
answer = min([sorted(q)[i:i+4] for i in range(len(q) - 3)], key=lambda w: w[3] - w[0])
assert answer[3] - answer[0] < 4
Written out and annotated:
sorted_q = sorted(q)
if len(q) < 4:
raise RuntimeError("Need at least four members in the list!")
windows = [sorted_q[i:i+4] for i in range(len(q) - 3)] # All the chunks of four elements
def size(window):
"""The size of the window."""
return window[3] - window[0]
answer = min(windows, key=size) # The smallest window, by size
if answer[3] - answer[0] > 3:
return "No group of four elements has a maximum distance of 1"
return answer
This would be one easy approach to find four closest numbers in list
# Lets have a list of numbers. It have to be at least 4 numbers long
numbers = [10, 4, 9, 1,7,12,25,26,28,29,30,77,92]
numbers.sort()
#now we have sorted list
delta = numbers[4]-numbers[0] # Lets see how close first four numbers in sorted list are from each others.
idx = 0 # Let's save our starting index
for i in range(len(numbers)-4):
d = numbers[i+4]-numbers[i]
if d < delta:
# if some sequence are closer together we save that value and index where they were found
delta = d
idx = i
if numbers[idx:idx+4] == 4:
print ("closest numbers are {}".format(numbers[idx:idx+4]))
else:
print ("Sequence with defined difference didn't found")
Here is my jab at the issue for OP's reference, as #kojiro and #ex4 have already supplied answers that deserve credit.
def find_neighbor(nums, dist, k=4):
res = []
nums.sort()
for i in range(len(nums) - k):
if nums[i + k - 1] - nums[i] <= dist * k:
res.append(nums[i: i + k])
return res
Here is the function in action:
>>> nums = [10, 11, 5, 6, 7, 8, 9] # slightly modified input for better demo
>>> find_neighbor(nums, 1)
[[5, 6, 7, 8], [6, 7, 8, 9], [7, 8, 9, 10]]
Assuming sorting is legal in tackling this problem, we first sort the input array. (I decided to sort in-place for marginal performance gain, but we can also use sorted(nums) as well.) Then, we essentially create a window of size k and check if the difference between the first and last element within that window are lesser or equal to dist * k. In the provided example, for instance, we would expect the difference between the two elements to be lesser or equal to 1 * 4 = 4. If there exists such window, we append that subarray to res, which we return in the end.
If the goal is to find a window instead of all windows, we could simply return the subarray without appending it to res.
You can do this in a generic fashion (i.e. for any size of delta or resulting largest group) using the zip function:
def deltaGroups(aList,maxDiff):
sList = sorted(aList)
diffs = [ (b-a)<=maxDiff for a,b in zip(sList,sList[1:]) ]
breaks = [ i for i,(d0,d1) in enumerate(zip(diffs,diffs[1:]),1) if d0!=d1 ]
groups = [ sList[s:e+1] for s,e in zip([0]+breaks,breaks+[len(sList)]) if diffs[s] ]
return groups
Here's how it works:
Sort the list in order to have each number next to the closest other numbers
Identify positions where the next number is within the allowed distance (diffs)
Get the index positions where compliance with the allowed distance changes (breaks) from eligible to non-eligible and from non-eligible to eligible
This corresponds to start and end of segments of the sorted list that have consecutive eligible pairs.
Extract subsets of the the sorted list based on the start/end positions of consecutive eligible differences (groups)
The deltaGroups function returns a list of groups with at least 2 values that are within the distance constraints. You can use it to find the largest group using the max() function.
output:
q = [10,11,5,6,7,8]
m = deltaGroups(q,1)
print(q)
print(m)
print(max(m,key=len))
# [10, 11, 5, 6, 7, 8]
# [[5, 6, 7, 8], [10, 11]]
# [5, 6, 7, 8]
q = [15,1,9,3,6,16,8]
m = deltaGroups(q,2)
print(q)
print(m)
print(max(m,key=len))
# [15, 1, 9, 3, 6, 16, 8]
# [[1, 3], [6, 8, 9], [15, 16]]
# [6, 8, 9]
m = deltaGroups(q,3)
print(m)
print(max(m,key=len))
# [[1, 3, 6, 8, 9], [15, 16]]
# [1, 3, 6, 8, 9]

Appending sum of last two integers of a list to that same list

Edit: I should add that the focus for this problem is on the use of 'side effects' in a function.
I am submitting my code on my computing course on Coursera. I think I must be missing something super obvious. When I submit my code I get the following error:
#TEST 5#
append_fibonacci(args) returned None
** ERROR ** side effect from: [-5, 8, 1] to: [-5, 8, 1]
* EXPECTED: * [-5, 8, 3]
inputs:
outputs:
I mostly just don't understand what the feedback is telling me. I'm a bit of a newbie, having just started Python this month. Thank you for your help!
The following is my code:
def append_fibonacci(integer_list):
# Modify the argument list of integers by
# appending a new integer that is the sum
# of the last two integers in the list.
# If the list has fewer than two elements
# add the int object 1 to the list.
if len(integer_list) > 2:
sum_last_two = ((integer_list[-1]) + (integer_list[-2]))
integer_list.append(sum_last_two)
else:
integer_list.append(1)
def main():
# Call the append_fibonacci function on this
# list: [3, 5, 8] and output the result object
number_list = [3, 5, 8]
append_fibonacci(number_list)
print(number_list)
main()
The error message you got is saying that the updated list should be [-5, 8, 3] but you're setting it to [-5, 8, 1]
This is because your function doesn't work correctly if the list has exactly 2 elements. It should add the two elements and append the sum (-5 + 8 = 3), but it will instead just append 1.
if len(integer_list) > 2:
should be:
if len(integer_list) >= 2:

What is wrong with my sorting algorithm?

I am a beginner programmer and I've been trying to create my own sorting algorithm in Python, I don't understand why it outputs only some of the numbers that were present in the input. I put debug prints everywhere to understand the problem but still got nothing. The code should find and move to the final array the biggest number of the input array, and do that until the input array it's empty, but it seems to stop at some point. There was a person with a similar problem but the solution did not apply to me as well. This is the code:
array = [3, 6, 25, 4, 5, 24, 7, 15, 5, 2, 0, 8, 1] #just random numbers
output = []
while(len(array) > 0):
maximum = 0
for x in array:
maximum = max(maximum, x)
output.append(maximum)
tempArray = []
for x in array:
temp = array.pop()
if(temp < maximum):
tempArray.append(temp)
array = tempArray
print(output)
The problem is here:
for x in array:
temp = array.pop()
You're modifying the same list that you're iterating over. That's going to cause trouble.
Consider what happens when 5 is the maximum (and there are two 5s in the input.) One 5 gets added to output, the rest of the 5s are never added to tempArray.
To diagnose, put some debug prints in the loop, such as print(output, array) at the end of the outer loop. And maybe more in the inner loop. After seeing the problem (removing two things from array each inner iteration, this works.
array = [3, 6, 25, 4, 5, 24, 7, 15, 5, 2, 0, 8, 1] #just random numbers
output = []
while(array):
maximum = 0
for x in array:
maximum = max(maximum, x)
output.append(maximum)
tempArray = []
for x in array:
if(x < maximum):
tempArray.append(x)
array = tempArray
print(output)
There are, of course, easier and better ways to delete the max from array, and only delete one copy of max instead of all.

Categories