I am quite new to Python and trying to learn algorithms, I wanted to ask why is it logically wrong if I use low < hi when looking over the list, the correct logical operation is low <= hi, what is the edge case it is preventing.
def binary_search(input_array, value):
"""Your code goes here."""
#O(log(n))
low = 0
hi = len(input_array) - 1
while low <= hi: #why cant it be low < hi
mid = (low + hi)//2
if input_array[mid] == value:
return mid
elif input_array[mid] < value:
print(low, hi)
low = mid + 1
else:
hi = mid - 1
return -1
test_list = [1,3,9,11,15,19,29]
test_val1 = 25
test_val2 = 15
print(binary_search(test_list, test_val1))
print(binary_search(test_list, test_val2))
Consider you have only one element [1] and you are searching for 1.
< : return -1 Since you would just skip the loop
<= : return the correct value
When your target is, for example in your case, 15:
1st iteration indexes: low == 4, hi == 6
2nd iteration indexes: low == 4, hi == 4
If you use low < high you won't drop into your loop on the second iteration, since 4 < 4 returns false. Your program will think it can't find the value even though the value is on index 4.
You could if you had written
hi = len(input_array)
while low < hi: # now this works.
...
In fact generally this it's how I generally write these loops. Take advantage of the fact that len() will always be 1 more than the last index, and run the loop only while the index is < that value.
If you subtract 1 from the len(input_array) so that the max value for hi is the last index in the array, then in order to ruin the loop on that last element you need the = part of low <= hi
Generally it's (mentally) easier to instead set hi = len(input_array), which is 1 past the last index, and then run the loop only while low<hi .
(Less typing, and less mental gymnastics).
In this case, once low==hi we've gone past the last index, and it would then be out of bounds.
The "edge case" you refer to is that you want to make sure to run the loop on all elements (indexes).
So one way or another, you need to look at that last index/element out the array.
Basically there are two common ways to code this, but you cannot mix them up. Make sure your end condition is with respect to your initial condition.
What you set hi to (either the length of the array, or the last index of the array) determines whether you are going to use low < hi or low <= hi
We use <= instead of just < because in the case that the desired value happens to be the last index (when low=hi), we will need to include an equal sign to check for that case.
Related
Hi I am doing DSA problems and found a problem called as ceiling of the element in sorted array. In this problem there is a sorted array and if the target element is present in the sorted array return the target. If the target element is not found in the sorted array we need to return the smallest element which is greater than target. I have written the code and also done some test cases but need to check if everything works correctly. This problem is not there on leetcode where I could run it with many different cases. Need suggestion/feedback if the problem is solved in the correct way and if it would give correct results in all cases
class Solution:
#My approch
def smallestNumberGreaterThanTarget(self, nums, target):
start = 0
end = len(nums)-1
if target > nums[end]:
return -1
while start <= end:
mid = start + (end-start)//2
if nums[mid] == target:
return nums[mid]
elif nums[mid] < target:
if nums[mid+1] >= target:
return nums[mid+1]
start = mid + 1
else:
end = mid-1
return nums[start]
IMO, the problem can be solved in a simpler way, with only one test inside the main loop. The figure below shows a partition of the real line, in subsets associated to the values in the array.
First, we notice that for all values above the largest, there is no corresponding element, and we will handle this case separately.
Now we have exactly N subsets left, and we can find the right one by a dichotomic search among these subsets.
if target > nums[len(nums)-1]:
return None
s, e= 0, len(nums);
while e > s:
m= e + ((s - e) >> 1);
if target > nums[m]:
s= m+1
else:
e= m
return s
We can formally prove the algorithm using the invariant nums[s-1] < target <= nums[e], with the fictional convention nums[-1] = -∞. In the end, we have the bracketing nums[s-1] < target <= nums[s].
The code errors out with an index out-of-range error for the empty list (though this may not be necessary because you haven't specified the problem constraints).
A simple if guard at the top of the function can fix this:
if not nums:
return -1
Otherwise, it seems fine to me. But if you're still not sure whether or not your algorithm works, you can always do random testing (e.g. create a linear search version of the algorithm and then randomly generate inputs to both algorithms, and then see if there's any difference).
Here's a one-liner that you can test against:
input_list = [0, 1, 2, 3, 4]
target = 0
print(next((num for num in input_list if num >= target), -1))
This is for a school assignment.
I have been tasked to define a function determining the largest square pyramidal number up to a given integer(argument). For some background, these are square pyramidal numbers:
1 = 1^2
5 = 1^2+2^2
14 = 1^2+2^2+3^2
So for a function and parameter largest_square_pyramidal_num(15), the function should return 14, because that's the largest number within the domain of the argument.
I get the idea. And here's my code:
def largest_square_pyramidal_num(n):
sum = 0
i = 0
while sum < n:
sum += i**2
i += 1
return sum
Logically to me, it seemed nice and rosy until I realised it doesn't stop when it's supposed to. When n = 15, sum = 14, sum < n, so the code adds one more round of i**2, and n is exceeded. I've been cracking my head over how to stop the iteration before the condition sum < n turns false, including an attempt at break and continue:
def largest_square_pyramidal_num(n):
sum = 0
for i in range(n+1):
sum += i**2
if sum >= n:
break
else:
continue
return sum
Only to realise it doesn't make any difference.
Can someone give me any advice? Where is my logical lapse? Greatly appreciated!
You can do the following:
def largest_pyr(x):
pyr=[sum([i**2 for i in range(1,k+1)]) for k in range(int(x**0.5)+1)]
pyr=[i for i in pyr if i<=x]
return pyr[-1]
>>>largest_pyr(15)
14
>>> largest_pyr(150)
140
>>> largest_pyr(1500)
1496
>>> largest_pyr(15000)
14910
>>> largest_pyr(150000)
149226
Let me start by saying that continue in the second code piece is redundant. This instruction is used for scenario when you don't want the code in for loop to continue but rather to start a new iteration (in your case there are not more instructions in the loop body).
For example, let's print every number from 1 to 100, but skip those ending with 0:
for i in range(1, 100 + 1):
if i % 10 != 0:
print(i)
for i in range(1, 100 + 1):
if i % 10 == 0:
# i don't want to continue executing the body of for loop,
# get me to the next iteration
continue
print(i)
The first example is to accept all "good" numbers while the second is rather to exclude the "bad" numbers. IMHO, continue is a good way to get rid of some "unnecessary" elements in the container rather than writing an if (your code inside if becomes extra-indented, which worsens readability for bigger functions).
As for your first piece, let's think about it for a while. You while loop terminates when the piramid number is greater or equal than n. And that is not what you really want (yes, you may end up with a piramid number which is equal to n, but it is not always the case).
What I like to suggest is to generate a pyramid number until in exceedes n and then take a step back by removing an extra term:
def largest_square_pyramidal_num(n):
result = 0
i = 0
while result <= n:
i += 1
result += i**2
result -= i ** 2
return result
2 things to note:
don't use sum as a name for the variable (it might confuse people with built-in sum() function)
I swapped increment and result updating in the loop body (such that i is up-to-date when the while loop terminates)
So the function reads like this: keep adding terms until we take too much and go 1 step back.
Hope that makes some sense.
Cheers :)
SUMMER OF '69: Return the sum of the numbers in the array, except ignore sections of numbers starting with a 6 and extending to the next 9 (every 6 will be followed by at least one 9). Return 0 for no numbers.¶
I have tried to use the pop method but it didn't work. I want to know why.
def summer_69(arr):
num=(6,7,8,9)
if num not in arr:
return sum(arr)
if num in arr:
arr.pop(num)
return sum(arr)
print(summer_69([4,5,6,7,8,9]))
I am getting the whole sum like in this one I am getting like 39.
Pop removes the item at a specified index. Max index in your array is 5 (4 has index 0).
I recommend finding the index of 6 via
arr.index(6)
Not efficient, but you can then repeatedly pop that index until it becomes 9, (and popping once more if "extending to" means including the 9).
Instead of doing this i will suggest you to use a single loop as it will be more efficient. Use single loop and keep adding number to sum until 6 is encountered. As soon as 6 comes skip the numbers till a 9 occurs. Again start to add numbers to sum. Runs in O(n).
i=0
Sum =0
While i < length:
If a[i] == 6:
While i < length and a[i] != 9:
i += 1
Else:
Sum += a[i]
i += 1
return Sum
I am trying to understand this function with little to no avail. I completely understand what a binary search is but am only new to the concept of recursion but do have a slight grasp on it. I don't really understand what the default values of low and high would be when first calling the function. As of right now I am just including the search space I know the number is in, but what if I don't or I am not sure of the list length? Otherwise, I understand the recursion process going on here as well as the need for low and high being arguments. The function below is provided in the notes by an online course I am taking; however, it wasn't explained in the lecture and contains no docstrings or references about it.
def bSearch(L, e, low, high):
if high - low < 2:
return L[low] == e or L[high] == e
mid = low + int((high-low)/2)
if L[mid] == e:
return True
if L[mid] > e:
return bSearch(L, e, low, mid-1)
else:
return bSearch(L, e, mid+1, high)
L = [1,3,6,15,34,84,78,256]
print bSearch(L, 15, 4, 8)
print bSearch(L, 84, 0, 6)
Output:
False
True
High and low appear to be indices for which part of the list to search.
In the first example, 15 has an index of 3, so specifying a lower index of 4 means the 15 isn't included in the search space. In the second example, 84 has an index of 5, so it is included in the search space spanning indices 0 and 6.
These indices are also inclusive. If the second example were:
print bSearch(L, 84, 0, 5)
the answer would be:
True
If you want to search the entire list, you can simply do:
print bSearch(L, 84, 0, len(L) - 1)
where the - 1 is necessary because the search function is inclusive.
Binary search .
bsearch(list , element to be found , start index , end index).
start index can be taken as 0 at the start of the function
and last index can be taken as len(list)-1
As in question for bsearch(L,15 , 4 , 8 ).
U are searching only between 5th and 9th element where the number is not present.
In the second function call u are searching between first element and 5 th element where a number present.
U can call this function as bsearch(L , 15 ,0 , len(L) - 1) for any other number.
Hope this helps.
low and high specify the indices of L where the algorithm must search. In the first example, 15 has the index 3. This is not in the interval [4,8] so it will return false. In the second example the index of 84 in L is 5, this is in the interval [0,6] so this will return True.
If the number you are searching for is not in L this method will return False. Why? Because you end up in the base case of if (high-low) < 2. In this case there will be checked against L[high] or L[low] being equal to the number you are searching for. If both are not the case, it returns False. This is the definition of the logical or.
False or False = False
False or True = True
True or False = True
True or True = True
If you are not sure about the list length, this will produce an error if the high or low value you provide are not in the range of L. You can add an extra condition so this can not happen, but I think that is out of the scope of that lesson. :)
I want to achieve it in O(log n) complexity.using the idea of binary search it can be achieved . I put it this way: let L be a list ;L= [12,10,9,7,6,5,8,9,11] so the expected outcome should be 5. Is there a simple algorithm in python to do it?
def binse(l,lo,hi):
n =len(l)
lo = 0
hi =n
mid =(lo+hi)//2
if (hi-lo)<2:
return lo
if l[mid]<l[mid-1] and l[mid]<l[mid+1]:
return l[mid]
elif l[mid]<l[mid-1] and l[mid]>l[mid+1]:
return binse(l,mid+1,hi)
elif l[mid]>l[mid-1] and l[mid]<l[mid+1]:
return binse(l,lo,mid)
else:
return
l = [13,11,5,6,7,8,9,11,13]
lo =0
hi =len(l)
print(binse(l,lo,hi))
def mini(lis):
s, e = 0, len(lis)-1
while True:
m = (s+e)/2
if lis[m] > lis[m+1]:
s = m
elif lis[m-1] < lis[m]:
e = m+1
else:
return m
This should work.
You said it - use binary search. At each probe, take two successive entries and determine whether the values are increasing or decreasing. Based on that, you know which region to further sub-divide.
BTW, I'm ignoring the case of equality
Update based on posted code:
The first thing binse does is overwrite the lo and hi parameters - bad!
The return lo line is clearly wrong as it is not returning an array value
Need to be a bit careful about some edge cases including getting near the endpoints and also when you have a sequence of equal values
The last "return" is also clearly wrong as it has no value.
The first item is the reason you are seeing the infinite recursion.