Binary Searching in Python - python

So I have this problem.
You are given a landscape in the form of a non-empty one-dimensional
array seq. The goal is to find an index i of a cell that is a pit. We
say seq[i] is a pit if seq[i] <= seq[i-1] and seq[i] <= seq[i+1]. For
example in the array [7, 6, 9, 7, 8], the indices 1 and 3 are pits.
The first or last elements are considered to be a pit if they are less
than or equal to their only neighbour. For example the last element of
[3, 2, 4, 4, 1] is a pit (and also index 1). Note that the definition
of a pit also includes equality; for example in [3, 2, 2, 2, 5, 6, 6,
8], the indices 1, 2, 3, and 6 are pits. As a special case, we also
define the only cell of an array of length one to be a pit as well.
I've formulated a solution using a binary search (kind of) to achieve O(logn) as the worst case time. But I've encountered an example which returns nothing or NONE.
def find_pit(seq):
first = 0
last = len(seq) - 1
origlast = last
mid = 0
if len(seq) == 1 :
return 0
else:
while first <= last & mid < last :
mid = (first + last) // 2
if seq[mid] <= seq[mid - 1] & seq[mid] <= seq[mid + 1]:
return mid
else:
if seq[mid] > seq[mid - 1]:
last = mid
else:
first = mid
if seq[0] <= seq[1]:
return 0
elif seq[origlast] <= seq[origlast-1]:
return (len(seq) - 1)
print(find_pit([0,1]))
print(find_pit([5, 4, 3, 6, 7]))
How do I fix this?

You need to change the
& (bitwise "and")
to
and (logical "and")
in your code:
def find_pit(seq):
first = 0
last = len(seq) - 1
origlast = last
mid = 0
if len(seq) == 1 :
return 0
else:
#change next line to use logical and
while first <= last and mid < last :
mid = (first + last) // 2
#change next line to use logical and
if seq[mid] <= seq[mid - 1] and seq[mid] <= seq[mid + 1]:
return mid
else:
if seq[mid] > seq[mid - 1]:
last = mid
else:
first = mid
if seq[0] <= seq[1]:
return 0
elif seq[origlast] <= seq[origlast-1]:
return (len(seq) - 1)
print(find_pit([0,1]))
print(find_pit([5, 4, 3, 6, 7]))
Running this with the above test cases will now give the result:
0 for the first list and 2 for the second.

seems to work at finding first pit in the given cases. I've tweaked the call to allow multiple functions to be checked.
#.... original find_pit left, but not pasted in
import sys
def find_pit2(seq):
left = sys.maxint
maxp = len(seq)
if maxp == 1 :
return 0
else:
for pos, current in enumerate(seq):
try:
right = seq[pos+1]
except IndexError:
#rightmost, count as right neighbor as bigger
right = sys.maxint
#pit - smaller or equal to neighbours
if left >= current and current <= right:
return pos
left = current
li_f = [find_pit, find_pit2]
for f in li_f:
print f.__name__
print(" ",f([0,1]))
print(" ",f([5, 4, 3, 6, 7]))
print(" ",f([7, 6, 9, 7, 8]))
print(" ",f([3, 2, 2, 2, 5, 6, 6, 8]))
giving
find_pit
(' ', 0)
(' ', 2)
(' ', None)
(' ', 3)
find_pit2
(' ', 0)
(' ', 2)
(' ', 1)
(' ', 1)

Related

Counting Shifts in Merge Sort Error with one array

I was trying to count the number of shifts that happen with merge sort when I ran into a problem. When I run the code with multiple arrays, for some reason one of the arrays states that 3 shifts happened when in reality its 4. I will greatly appreciate it if anyone can help me figure out what the problem is. Thanks
def mergeSort(arr):
count = x = y = 0
result =[]
arrayLength = len(arr)
if arrayLength <= 1:
return count
middle = arrayLength // 2
left = arr[:middle]
right = arr[middle:]
leftLength = len(left)
rightLength = len(right)
count += mergeSort(left)
count += mergeSort(right)
while x < leftLength and y < rightLength:
if left[x] <= right[y]:
result.append(left[x])
x += 1
else:
result.append(right[y])
y += 1
count += len(left[x:])-x
return count
arr = [1,20,6,4,5]
print(mergeSort(arr))
arr2 = [4,3,2,1]
print(mergeSort(arr2))
arr3=[1, 1, 1, 2, 2]
print(mergeSort(arr3))
arr4=[2, 1, 3, 1, 2]
print(mergeSort(arr4))
arr5 = [12,15,1,5,6,14,11]
print(mergeSort(arr5))
arr6=[3, 5, 7, 11, 9]
print(mergeSort(arr6))
result = mergeSort(arr)
print(result)
You have two bugs:
Your len(left[x:])-x subtracts x twice.
You're not actually sorting the given array but just building a result that you never use. The sorting is important for the upper call levels to count correctly.
Fixed and with better testing (Try it online!):
from itertools import combinations
def mergeSort(arr):
count = x = y = 0
arrayLength = len(arr)
if arrayLength <= 1:
return count
middle = arrayLength // 2
left = arr[:middle]
right = arr[middle:]
leftLength = len(left)
rightLength = len(right)
count += mergeSort(left)
count += mergeSort(right)
for write in range(arrayLength):
if y == rightLength or x < leftLength and left[x] <= right[y]:
arr[write] = left[x]
x += 1
else:
arr[write] = right[y]
y += 1
count += len(left) - x
return count
def naive(arr):
return sum(a > b for a, b in combinations(arr, 2))
def test(arr):
expect = naive(arr)
result = mergeSort(arr)
print(result == expect, expect, result, arr)
test([1, 20, 6, 4, 5])
test([4, 3, 2, 1])
test([1, 1, 1, 2, 2])
test([2, 1, 3, 1, 2])
test([12, 15, 1, 5, 6, 14, 11])
test([3, 5, 7, 11, 9])
I presume your question really isn't about counting the recursion and more about figuring out why your algorithm is not correct. When you're checking for while x < leftLength and y < rightLength: you are dropping items at the end of one of the lists. This should be an or not an and to make sure you are doing ALL items in both left and right lists. Something like this:
while x < leftLength or y < rightLength:
if x == leftLength:
result.append(right[y])
y += 1
continue
if y == rightLength:
result.append(left[x])
x += 1
continue
if left[x] <= right[y]:
result.append(left[x])
x += 1
else:
result.append(right[y])
y += 1
return result
and you can't return the counts like Frank said because you stop doing the merge sort, and writing back to arr doesn't work in python as it's not an in/out variable. You would have to have a global variable outside the class to do the counting.

How do I get the index of the largest element adjacent to the greatest element in a list

So I have stock = [5,6,8,4,8,3,6,4]. I want to get the index of the greatest element adjacent to the 1st occurrence of the greatest element, 8. So what I want to get will be 6 with index 1. I have tried using this code.
closest = min(range(len(stock)), key=lambda i: abs(stock[i]-max(stock)))
but it just returns the max element.
If I understood your problem correctly, the most interesting input would look like [1,5,8,4,8,7,6,4]. I.e. we need to return the index of the 5 since it's a second maximum closest to the first occurrence of the maximum. If so, then the algorithm would look as follows:
find two leftmost and absolute maximums m1 and m2
if m1 == m2 then the target is in either of two subarrays:
[0, pos(m1))
[pos(m1) + 1, pos(m2))
otherwise, the target is in either of the following subarrays:
[0, pos(m1))
[pos(m1) + 1, len(arr))
We can find k max elements in an array in a close to linear time using the binary heap. So, I think I got a linear solution for you:
import heapq
def nlargest_with_pos(n, arr):
assert len(arr) >= n
largest = heapq.nlargest(n, ((it[1], -it[0]) for it in enumerate(arr)))
return [(it[0], -it[1]) for it in largest]
def find_x(arr):
assert len(arr) > 1
first_max, second_max = nlargest_with_pos(2, arr)
if len(arr) == 2:
return second_max[1]
left_range = (0, first_max[1])
if second_max[0] == first_max[0]:
right_range = (first_max[1] + 1, second_max[1])
else:
right_range = (first_max[1] + 1, len(arr))
left_hand = arr[left_range[0]:left_range[1]]
right_hand = arr[right_range[0]:right_range[1]]
if not left_hand:
return nlargest_with_pos(1, right_hand)[0][1]
if not right_hand:
return nlargest_with_pos(1, left_hand)[0][1]
left_second_max = nlargest_with_pos(1, left_hand)[0]
right_second_max = nlargest_with_pos(1, right_hand)[0]
if left_second_max[0] >= right_second_max[0]:
return left_second_max[1]
else:
return right_second_max[1]
print(find_x([1,5,8,4,8,7,6,4]))
Like this:
stock = [5,6,8,7,8,3,6,4]
if stock.index(max(stock)) == len(stock)-1:
print(len(stock)-2)
elif not stock.index(max(stock)):
print(1)
elif stock[stock.index(max(stock))-1] > stock[stock.index(max(stock))+1]:
print(stock.index(max(stock))-1)
else:
print(stock.index(max(stock))+1)
Output:
1
Not very elegant but this should work nonetheless:
stock.index(max(stock[stock.index(max(stock)) - 1], stock[(stock.index(max(stock)) + 1) % len(stock)]))
You'll have to add handling if there's a chance you see a list with less than three values
This prints index of highest element that's next to the first occurrence of maximum value in list stock:
stock = [1,5,8,4,8,7,6,4]
idx_max = stock.index(max(stock))
print(max([i for i in [idx_max-1, idx_max+1] if -1 < i < len(stock)], key=lambda k: stock[k]))
Prints:
1
Test cases:
stock = [8,3,8,4,8,3,6,4] # 1
stock = [1,3,1,3,8,5,6,4] # 5
stock = [1,3,1,4,1,3,6,8] # 6
stock = [1,5,8,4,8,7,6,4] # 1
Here's one way:
def get_max_neighbour(l):
_max = max(l)
excl = (-1, len(l))
neighbour = max(
(
(l[j], j)
for i in range(excl[-1])
for j in (i-1, i+1)
if l[i] == _max and j not in excl
),
key=lambda x: x[0]
)
return neighbour[1]
Result:
1
The nice thing about this is you can return both the value and index if you want.
Here's my solution to this puzzle. I'd say it is most similar to the solution of #DavidBuck, in that [8] -> -1 and [] -> None, but has four fewer exit points:
from math import inf
def index_of_max_neighbor_of_max(array):
if not array:
return None
index = array.index(max(array))
a = array[index - 1] if index - 1 >= 0 else -inf
b = array[index + 1] if index + 1 < len(array) else -inf
return index + (b > a) * 2 - 1
And my test code:
if __name__ == "__main__":
iomnom = index_of_max_neighbor_of_max
print(iomnom([5, 6, 8, 4, 8, 3, 6, 4])) # 1
print(iomnom([5, 6, 8, 4, 8, 3, 6, 9])) # 6
print(iomnom([5, 3])) # 1
print(iomnom([3, 5])) # 0
print(iomnom([8])) # -1
print(iomnom([])) # None
print(iomnom([5, 6, 8, 7, 8, 3, 6, 4])) # 3
print(iomnom([5, 6, 8, 4, 8, 3, 6, 9])) # 6
print(iomnom([5, 4, 8, 6, 8, 3, 6, 4])) # 3
def max_neighbor_index(l: list):
max_number = max(l)
max_number_indexes = [x for x in range(0, len(l)) if l[x] == max_number]
result = []
for number_index in max_number_indexes:
if number_index == 0:
result.append(1)
elif number_index == len(l) - 1:
result.append(len(l) - 2)
else:
result.append(l.index(max([l[number_index - 1], l[number_index + 1]])))
max_neighbor = max([l[x] for x in result])
return [x for x in result if l[x] == max_neighbor][0]
stock = [5, 6, 8, 4, 8, 3, 6, 4]
print(max_neighbor_index(stock))
1
stock = [5,6,8,4,8,3,6,4]
idx = 1
tmp,nxt = stock[0:2]
for i in range(1, len(stock)):
buf = stock[i-1] if i == len(stock)-1 else max(stock[i-1], stock[i+1])
if tmp < stock[i] or (tmp == stock[i] and nxt < buf):
idx = stock.index(buf, i-1)
nxt = stock[idx]
tmp = stock[i]
print('greatest next to greatest', nxt, 'at', idx)
Not very pretty, but it will cater for all possible scenarios, i.e. your list, max value a the start or the end, two or one value list.
Code is:
def highest_neighbour(stock):
if stock:
x = stock.index(max(stock))
if x - 1 >= 0:
if x + 1 < len(stock):
if stock[x + 1] > stock[x - 1]:
return x + 1
return x - 1
elif x + 1 < len(stock):
return x + 1
else:
return -1
I've set it to return -1 if the list only has one entry.
Output is:
highest_neighbour([5,6,8,4,8,3,6,4]) # -> 1
highest_neighbour([5,6,8,4,8,3,6,9]) # -> 6
highest_neighbour([9,6,8,4,8,3,6,4]) # -> 1
highest_neighbour([3,5]) # -> 0
highest_neighbour([8]) # -> -1
highest_neighbour([]) # -> None

Using binary search to find the last position of an element

I'm trying to write a function that returns the last position of an element in a sorted list, however I'm not quite sure of how binary search works. Can someone help me fix the problem in my code?
def last_entry(L, target):
low = 0
high = len(L) - 1
while low <= high:
mid = (low + high) // 2
if L[mid] < target:
low = mid + 1
elif L[mid] > target:
high = mid - 1
else:
if mid == len(L) - 1:
return mid
if target < L[mid + 1]:
return mid
return -1
The problem lies here:
else:
if mid == len(L) - 1:
return mid
if target < L[mid + 1]:
return mid
It's incomplete. If you use the input list [0, 0, 1, 3, 3, 3, 4, 8] and target 3, it will loop infinitely (I haven't tested it though). So you need to keep searching in the upper half for the target:
else:
if mid == len(L) - 1:
return mid
if target < L[mid + 1]:
return mid
low = mid + 1
Edit: I tested my solution and I think it works
You can separate the problems. First we do the binary search, then we verify if it's the latest occurrence in list.
lst = [1, 2, 2, 2, 3, 4, 4, 5, 6, 7, 7, 7, 8, 8, 9]
def search(lst, target):
min = 0
max = len(lst)-1
avg = (min+max)//2
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)
return avg
def find_latest(lst, index):
current = lst[index]
index += 1
while lst[index] == current:
current = lst[index]
index += 1
return index - 1
find_latest(lst ,search(lst, 4))

Find consecutive integers in a list

I am trying to find consecutive values from an unsorted list. Experimental code below:
num = [8, 9, 4, 1, 2, 3]
#(num[0+1]) next value
for i in range(len(num)-1): # not using -1 will cause index error
if num[i]+1==num[i+1]:
print('Con',num[i])
Problem: I am unable to get the last value with this current code. My output excludes the last value. Here is what I get (no 9 or no 3):
Con 8
Con 1
Con 2
I have seen a few complex solutions which were a little difficult for me to understand. Is it possible to tweak the for loop part a little and get the entire sequence? Thanks a lot.
You can use the function groupby:
from itertools import groupby
num = [8, 9, 4, 1, 2, 3]
# Enumerate and get differences between counter—integer pairs
# Group by differences (consecutive integers have equal differences)
gb = groupby(enumerate(num), key=lambda x: x[0] - x[1])
# Repack elements from each group into list
all_groups = ([i[1] for i in g] for _, g in gb)
# Filter out one element lists
list(filter(lambda x: len(x) > 1, all_groups))
# [[8, 9], [1, 2, 3]]
This is because you only check the next number. When you want the second number (like 9 or 3), you have to include a check for the previous number too. This will make the if a bit longer, but it'll work.
num=[8,9,4,1,2,3]
for i in range(len(num)):
if (
( # check for the next number
i + 1 != len (num) and # don't check the end of the list
num[i]+1==num[i+1]
) or ( # check for the previous number
i != 0 and # don't check before the list
num [i-1] == num [i] - 1
)
): print('Con',num[i])
Also, I had to remove the -1 in your range, because I already do a manual check, and as pointed out, this prvented 3 from being shown.
Your code only tests in one direction (being followed by a consecutive number).
For the full sequence you have to test in both direction.
num=[8,9,4,1,2,3]
assert(len(num) > 1)
for i, n in enumerate(num):
if i != 0:
if n == num[i-1] + 1:
print("Con", n)
continue
if i != len(num) - 1:
if n == num[i+1] - 1:
print("Con", n)
One way would be to print both numbers when you found them to be consecutive, but also check that the one at index i-1 was not in the consecutive list as well so that the number at index i is not printed twice:
num = [8, 9, 4, 1, 2, 3]
for i in range(len(num)-1): # not using -1 will cause index error
if num[i] + 1 == num[i + 1]:
if i == 0 or (i - 1 >= 0 and num[i - 1] != num[i] - 1):
print('Con', num[i])
print('Con', num[i + 1])
Could try with a more complex list as well:
num = [8, 9, 4, 1, 2, 3, 4, 4, 8, 9, 1, 2, 3, 0, 1, 5, 6, 1]
for i in range(len(num)-1): # not using -1 will cause index error
if num[i] + 1 == num[i + 1]:
if i == 0 or (i - 1 >= 0 and num[i - 1] != num[i] - 1):
print('Con', num[i])
print('Con', num[i + 1])
num = [8, 9, 4, 1, 2, 3]
def con(rng, pos=0):
if pos < len(rng):
if (pos > 0 and rng[pos]-1 == rng[pos-1]) or (pos < len(rng) -1 and rng[pos]+1 == rng[pos+1]):
print("con", rng[pos])
con(rng, pos+1)
con(num)
edit:
this is solution is based on concurrent function, and needs only the list as argument. As long as they are within lower-/upperbound of list, the function will check if (previous number)-1 or (next number)+1 are equal (this number)
output will be:
con 8
con 9
con 1
con 2
con 3

Why display my list index out of range

i am coding binary search, I define a function ,and have 4 parameter, one of the parameter that number is the number you want to search ,but when i
input a big number which bigger than the last number of list ,the compiler will display the list index out of range, what is the connection between number and index?
list = [2, 3, 4, 6, 8, 9, 10, 11, 20]
mid = len(list) / 2
left = 0
right = len(list)
def searchNumber(left, right, number, mid):
while left <= right:
mid = (right - left) / 2 + left
if list[mid] == number:
print("the local is in %d" % (mid))
return mid
break
elif list[mid] > number:
right = mid - 1
else:
left = mid + 1
list = [2, 3, 4, 6, 8, 9, 10, 11, 20]
mid = len(list) / 2
left = 0
right = len(list)
def searchNumber(left, right, number, mid):
**while left < right:**
mid = (right - left) / 2 + left
if list[mid] == number:
print("the local is in %d" % (mid))
return mid
break
elif list[mid] > number:
right = mid - 1
else:
left = mid + 1
I ran this code,
list = [2, 3, 4, 6, 8, 9, 10, 11, 20]
mid = len(list) / 2
left = 0
right = len(list)
def searchNumber(left, right, number, mid):
while left <= right:
mid = (right - left) / 2 + left
if list[mid] == number:
print("the local is in %d" % (mid))
return mid
break
elif list[mid] > number:
right = mid - 1
else:
left = mid + 1
return None
a = searchNumber(0, len(list)-1, 40, 40)
print "a =" + str(a)
b = searchNumber(0, len(list)-1, 9, 5)
print "b =" + str(b)
I got correct output in both the cases, where the asked number is present and the asking number is larger than the largest number of the list.
$ python pyt.py
a = None
the local is in 5
b = 5
I suspect the Error you get is because you're passing number which is negative or bigger than length-1 of the array, I don't see any other cases where you'll get "index out of range" Error.

Categories

Resources