Finding local minima and maxima - python

The implementation from Geeksforgeeks https://www.geeksforgeeks.org/find-indices-of-all-local-maxima-and-local-minima-in-an-array/ is wrong.
If you have consecutive-duplicates, things will gall apart!
Example 1: values = [ 1, 2, 3, 7, 11, 15, 13, 12, 11, 6, 5, 7, 11, 8]
The default implementation correctly identify "15" as a peak.
Example 2: values = [ 1, 2, 3, 7, 11, 15, 15, 13, 12, 11, 6, 5, 7, 11, 8]
The default implementation will mark "11" as local maxima because there are two consecutive 15's.
Below is code from geekforgeeks, with problem highlighted - when making greater/lesser comparison with left and right , if your neighbour's values == , then look further left or right:
def findLocalMaximaMinima(n, arr):
# Empty lists to store points of
# local maxima and minima
mx = []
mn = []
# Checking whether the first point is
# local maxima or minima or neither
if(arr[0] > arr[1]):
mx.append(0)
elif(arr[0] < arr[1]):
mn.append(0)
# Iterating over all points to check
# local maxima and local minima
for i in range(1, n-1):
# Condition for local minima
if(arr[i-1] > arr[i] < arr[i + 1]): <-- Problem is here
mn.append(i)
# Condition for local maxima
elif(arr[i-1] < arr[i] > arr[i + 1]): <-- Problem is here
mx.append(i)
# Checking whether the last point is
# local maxima or minima or neither
if(arr[-1] > arr[-2]):
mx.append(n-1)
elif(arr[-1] < arr[-2]):
mn.append(n-1)
# Print all the local maxima and
# local minima indexes stored
if(len(mx) > 0):
print("Points of Local maxima"\
" are : ", end ='')
print(*mx)
else:
print("There are no points of"\
" Local maxima.")
if(len(mn) > 0):
print("Points of Local minima"\
" are : ", end ='')
print(*mn)
else:
print("There are no points"\
" of Local minima.")

Here's the fix (For some reason I can't upload the code there, so upload here instead):
from datetime import datetime
from typing import List
def is_greater(index1, index2, values, increment_when_eq : bool):
if values[index1]>values[index2]:
return True
elif values[index1]<values[index2]:
return False
else:
# Case when: values[index1] == values[index2]
index2_shifted = index2+1 if increment_when_eq else index2-1
if index2_shifted < len(values):
return is_greater(index1=index1, index2 = index2_shifted, values = values, increment_when_eq = increment_when_eq)
else:
return False
def find_local_max_min(values : List):
mx = []
mn = []
n = len(values)
if n==0:
return None
if(values[0] > values[1]):
mx.append(0)
elif(values[0] < values[1]):
mn.append(0)
for i in range(1, n-1):
if (not is_greater(i, i-1, values, False) and not is_greater(i, i+1, values, True)):
mn.append(i)
elif(is_greater(i, i-1, values, False) and is_greater(i, i+1, values, True)):
mx.append(i)
if(values[-1] > values[-2]):
mx.append(n-1)
elif(values[-1] < values[-2]):
mn.append(n-1)
return {
'local_max' : mx,
'local_min' : mn
}
if __name__ == '__main__':
values = [ 1, 2, 3, 7, 11, 15, 15, 13, 12, 11, 6, 5, 7, 11 , 8, 19, 19, 18, 18, 18, 15, 7, 3]
start = datetime.now()
local_min_max = find_local_max_min(values)
local_max = local_min_max['local_max']
local_min = local_min_max['local_min']

I agree to Pranav that simply fix equality when making comparison will rectify the problem. The fix will be much more concise. Thanks Pranav
def findLocalMaximaMinima(n, arr):
mx = []
mn = []
if(arr[0] > arr[1]):
mx.append(0)
elif(arr[0] < arr[1]):
mn.append(0)
for i in range(1, n-1):
if(arr[i-1] >= arr[i] < arr[i + 1]):
mn.append(i)
elif(arr[i-1] < arr[i] >= arr[i + 1]):
mx.append(i)
if(arr[-1] > arr[-2]):
mx.append(n-1)
elif(arr[-1] < arr[-2]):
mn.append(n-1)
return mx, mn
arrs = [[ 1, 2, 3, 7, 11, 15, 15, 13, 12, 11, 6, 5, 7, 11, 8],
[4, 5, 6, 6, 6, 4, 3, 2, 1, 3, 5, 7, 9]]
for arr in arrs:
mx, mn = findLocalMaximaMinima(len(arr), arr)
print(*[f"{a}, {'max'*(i in mx)}, {'min' * (i in mn)}" for i, a in enumerate(arr)], sep='\n', end='\n\n')
https://tio.run/##bVLhboIwEP7PU1z8A3V1sTqdM7onmE/A#qMZZWsCJ6mYsBifnbVXVFggQK5fv##79u6q3/rniMu2zXQOucHs4/ilioNqTKkOBt0/QQ7KWraNwD1lA3tIZYgxxLQweeJY6VzCu6enQnaKoHpWVaUxS#aMQF08#LsRPvb4hOZHCwYMglX4rRPBAWeip#jszEy4A#zJ0dysDTzBwH6YwrD7xv1Y5LO72dwMR32ank#/EnQS0s0W47XwNxhW45H1nwYHGsKtrs8WnR1321HkRCffjhRcbRYclhxeOQi3EKvuc5BYBGzNYXUnbCSP0g0t1/S#kNxTKQjMNykj3wWXyPfB59tG3UzwMAvj81No9JdjYYxIUVmDdTJN88lFXTlc4lI18TShBpcNC5DBGKbQgciukzAEzsYjGs#ltqrW5C05nHS1jz8x5m4r85GLWdv#AQ

Related

How find all pairs equal to N in a list

I have a problem with this algorithm- I have to find pairs in list:
[4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
which are equal to 12. The thing is that after making a pair those numbers (elements) can not be used again.
For now, I have code which you can find below. I have tried to delete numbers from the list after matching, but I feel that there is an issue with indexing after this.
It looks very easy but still not working. ;/
class Pairs():
def __init__(self, sum, n, arr ):
self.sum = sum
self.n = n
self.arr = arr
def find_pairs(self):
self.n = len(self.arr)
for i in range(0, self.n):
for j in range(i+1, self.n):
if (self.arr[i] + self.arr[j] == self.sum):
print("[", self.arr[i], ",", " ", self.arr[j], "]", sep = "")
self.arr.pop(i)
self.arr.pop(j-1)
self.n = len(self.arr)
i+=1
def Main():
sum = 12
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
n = len(arr)
obj_Pairs = Pairs(sum, n, arr)
obj_Pairs.find_pairs()
if __name__ == "__main__":
Main()
update:
Thank you guys for the fast answers!
I've tried your solutions, and unfortunately, it is still not exactly what I'm looking for. I know that the expected output should look like this: [4, 8], [0, 12], [1, 11], [4, 8], [12, 0]. So in your first solution, there is still an issue with duplicated elements, and in the second one [4, 8] and [12, 0] are missing. Sorry for not giving output at the beginning.
With this problem you need to keep track of what numbers have already been tried. Python has a Counter class that will hold the count of each of the elements present in a given list.
The algorithm I would use is:
create counter of elements in list
iterate list
for each element, check if (target - element) exists in counter and count of that item > 0
decrement count of element and (target - element)
from collections import Counter
class Pairs():
def __init__(self, target, arr):
self.target = target
self.arr = arr
def find_pairs(self):
count_dict = Counter(self.arr)
result = []
for num in self.arr:
if count_dict[num] > 0:
difference = self.target - num
if difference in count_dict and count_dict[difference] > 0:
result.append([num, difference])
count_dict[num] -= 1
count_dict[difference] -= 1
return result
if __name__ == "__main__":
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
obj_Pairs = Pairs(12, arr)
result = obj_Pairs.find_pairs()
print(result)
Output:
[[4, 8], [8, 4], [0, 12], [12, 0], [1, 11]]
Demo
Brief
If you have learned about hashmaps and linked lists/deques, you can consider using auxiliary space to map values to their indices.
Pro:
It does make the time complexity linear.
Doesn't modify the input
Cons:
Uses extra space
Uses a different strategy from the original. If this is for a class and you haven't learned about the data structures applied then don't use this.
Code
from collections import deque # two-ended linked list
class Pairs():
def __init__(self, sum, n, arr ):
self.sum = sum
self.n = n
self.arr = arr
def find_pairs(self):
mp = {} # take advantage of a map of values to their indices
res = [] # resultant pair list
for idx, elm in enumerate(self.arr):
if mp.get(elm, None) is None:
mp[elm] = deque() # index list is actually a two-ended linked list
mp[elm].append(idx) # insert this element
comp_elm = self.sum - elm # value that matches
if mp.get(comp_elm, None) is not None and mp[comp_elm]: # there is no match
# match left->right
res.append((comp_elm, elm))
mp[comp_elm].popleft()
mp[elm].pop()
for pair in res: # Display
print("[", pair[0], ",", " ", pair[1], "]", sep = "")
# in case you want to do further processing
return res
def Main():
sum = 12
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
n = len(arr)
obj_Pairs = Pairs(sum, n, arr)
obj_Pairs.find_pairs()
if __name__ == "__main__":
Main()
Output
$ python source.py
[4, 8]
[0, 12]
[4, 8]
[1, 11]
[12, 0]
To fix your code - few remarks:
If you iterate over array in for loop you shouldn't be changing it - use while loop if you want to modify the underlying list (you can rewrite this solution to use while loop)
Because you're iterating only once the elements in the outer loop - you only need to ensure you "popped" elements in the inner loop.
So the code:
class Pairs():
def __init__(self, sum, arr ):
self.sum = sum
self.arr = arr
self.n = len(arr)
def find_pairs(self):
j_pop = []
for i in range(0, self.n):
for j in range(i+1, self.n):
if (self.arr[i] + self.arr[j] == self.sum) and (j not in j_pop):
print("[", self.arr[i], ",", " ", self.arr[j], "]", sep = "")
j_pop.append(j)
def Main():
sum = 12
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
obj_Pairs = Pairs(sum, arr)
obj_Pairs.find_pairs()
if __name__ == "__main__":
Main()

Equal step-slicing a list by keeping minimum and maximum

I need to get an "equal" step slicing from a linear integer list (which could not start from 0), but with the following requirements:
the last value (the maximum) always has to appear
there must not be an interval lower than the step (most importantly, between the next to last and last values)
given the point above, some intervals might be higher than the step, and those intervals must be placed equally between the resulting list
only standard library functions should be used (no numpy)
Some examples:
with a list from range(10) and step 2, the result should be one of the following:
[0, 2, 5, 7, 9]
[0, 2, 4, 7, 9]
with range(21) and step 3:
[0, 3, 7, 10, 14, 17, 20]
with range(1, 22) and step 3:
[1, 4, 8, 11, 15, 18, 21]
Right now I've got something similar to this, which obviously does not work properly:
def getSlices(l, s):
skipCount = (len(l) - 1) % s
divCount = int(len(l) / (skipCount + 1))
o = []
for delta, skip in enumerate(range(skipCount + 1)):
o.extend(l[skip * divCount + delta:(skip + 1) * divCount + delta:s])
return o
>>> getSlices(list(range(21)), 3)
[0, 3, 6, 8, 11, 14, 16, 19]
I know I could just cycle through all values, skip by correlating enumerate indexes and steps, and add a "delta" as soon as a new "portion" of the list is reached, but that doesn't seem the most performing solution.
I think this could go the way you want. Hope it helps.
def getSlices(l, step):
init = l[0]
last = l[-1] # exclude last element (remove as you want)
slices = (last-init) // step + 1
mod = (last-init) % step
even = mod // 2
mid = slices // 2 - 1
even_start = mid - even
even_end = mid + mod - even
final = []
val = init
for i in range(slices):
final.append(val)
val += step
# Distribute mod unitary in the middle
if slices-1 >= mod:
if mod > 0 and (even_start <= i <= even_end):
val += 1
# In case is the middle don't change it
if i == mid:
val += - 1
# Distribute mod evenly all across the slices
else:
val += mod // (slices-1)
# In case a there is mod left, place it just in the middle
if i == mid:
val += mod % (slices-1)
return final
# Examples:
#
# getSlices(list(range(10)), 2)
# [0, 2, 4, 7, 9]
#
# getSlices(list(range(21)), 3)
# [0, 3, 7, 10, 14, 17, 20]
#
# getSlices(list(range(1, 22)), 3)
# [1, 4, 8, 11, 15, 18, 21]
#
# getSlices(list(range(36)), 10)
# [0, 11, 24, 35]

Why isn't my insertion sort working?

I am trying to implement an insertion sort from reading the vague description, so I may very well be going about it the wrong way. However, from what I can see this should work:
import random
arr = []
c_num = 0
for i in range(0, 10):
arr.append(random.randint(0, 10))
def InsertionSort(unsorted_array, current_num):
smallest_num = unsorted_array[current_num]
smallest_num_index = 0
isSorted = True
for i in range(current_num, len(unsorted_array) - 1):
if unsorted_array[i] < smallest_num:
smallest_num = unsorted_array[i]
smallest_num_index = i
isSorted = False
if isSorted:
return unsorted_array
else:
temp = unsorted_array[current_num]
unsorted_array[current_num] = smallest_num
unsorted_array[smallest_num_index] = temp
current_num += 1
return InsertionSort(unsorted_array, current_num)
print(InsertionSort(arr, c_num))
It will sort the first few elements, and then just print out the now slightly-sorted array, but I can't see what I am missing to get the whole thing sorted.
Sample:
input = [9, 8, 5, 4, 5, 8, 2, 10, 7, 5]
output = [2, 4, 5, 8, 5, 8, 9, 10, 7, 5]

Insertion algorithm can not insert all nodes

I am newbie in Python. I want to insert new node into the current route to check whether it makes the route shorter or not. However my code doesn't run well, please show me the mistake. The steps are following:
1. Create random subtour (example: 0-2-0)
2. Get randomly the node which is not visited and check this node in each pair of nodes in current route. If the node satisfy the shorter requirement, we insert it into current node (example: 0-4-2-0).
3. Continue until all nodes inserted into the route.
import random
distMatrix = [
[100, 14, 20, 10, 35, 18, 5],
[6, 100, 7, 35, 17, 9, 24],
[8, 35, 100, 36, 27, 3, 15],
[21, 7, 12, 100, 7, 4, 26],
[33, 25, 6, 18, 100, 19, 11],
[6, 2, 22, 30, 9, 100, 8],
[24, 3, 12, 5,17, 16, 100],
]
def get_total_distance(route,d):
total = 0
for i in range (len(route)-1):
pre = route[i]
succ = route[i+1]
total += d[pre][succ]
return total
def insertion(d):
numNodes = len(d)
notVisited = list(range(1, numNodes))
first_random_node = random.choice(notVisited)
route = [0]
route.append(first_random_node)
notVisited.remove(first_random_node)
route.append(0) #create first subtour
print("1st",route)
location = 0
while len(notVisited) != 0:
for j in notVisited:
for i in range (len(route)-1):
pre = route[i]
succ = route[i+1]
check_route = d[pre][j] + d[j][succ]
current_distance = d[pre][succ]
if check_route <= current_distance:
print(j)
route.insert(i + 1, j)
notVisited.remove(j)
print("2nd", route)
return route
solution = insertion(distMatrix)
print("The solution for the route is:",solution)
print("The total distance is:", get_total_distance(solution,distMatrix))
As it is, your code doesn't run: there's a syntax error because you named a local function the same as a package you're trying to use: random. Inside your function random, you can no longer access the package of that name, because you redefined the name.
Once that is resolved, your code hangs with this output:
1st [0, 5, 0]
3
2nd [0, 3, 5, 0]
6
2nd [0, 6, 3, 5, 0]
You have a logic problem with this combination:
while len(notVisited) != 0:
print("WHILE", len(notVisited))
for j in notVisited:
....
if check_route <= current_distance:
print(j)
route.insert(i + 1, j)
notVisited.remove(j)
print("2nd", route)
Your only exit from the while loop is to remove everything from notVisited. There's no logical guarantee that will happen -- and when it doesn't, you're stuck in an infinite loop trying to find a shorter route that doesn't exist.

efficiently converting a bijection to cycle notation

Consider the following problem. Given is an array P of length n representing a bijection. That is element 0 <= i < n is mapped to P[i].
Given that P is a permutation, it can be written in cycle notation as described for example here.
For example if
P = [16, 3, 10, 6, 5, 9, 1, 19, 13, 14, 7, 0, 2, 8, 12, 4, 17, 15, 11, 18]
then the result in cycle notation would be
[(16, 17, 15, 4, 5, 9, 14, 12, 2, 10, 7, 19, 18, 11, 0), (3, 6, 1), (13, 8)]
Following is a Python method accomplishing this
def toCycle(p):
covered = cur = 0
perm = []
n = len(p)
done = [0]*n
while covered < n:
while cur < n and done[cur] == -1:
cur+=1
cycle = [p[cur]]
sec = p[p[cur]]
done[p[cur]] = -1
done[cur] = -1
covered+=1
while sec != cycle[0]:
cycle.append(sec)
done[sec] = -1
sec = p[sec]
covered+=1
perm+=[tuple(cycle)]
return perm
The algorithm clearly runs in linear time (each element of done/p is accessed a constant number of times) and hence there is not much that can be done asymptotically.
As I have to use this method on a large number of large permutations I was wondering
Can you make it faster? Do you have any suggestions for performance
improvement?
def cycling(p, start, done):
while not done[e]:
done[e] = True
e = p[e]
yield e
def toCycle(p):
done = [False]*len(p)
cycles = [tuple(cycling(p, 0, done))]
while not all(done):
start = done.index(False)
cycles.append(tuple(cycling(p, start, done)))
return cycles
With your example my code runs about 30% faster than yours.

Categories

Resources