Finding minimum peak elements from an array - python

Details:
Given an array numbers = {2, 7, 8, 5, 1, 6, 3, 9, 4}. Check the below conditions, both the conditions should be satisfied.
If arr[i – 1] < arr[i] > arr[i + 1], where 1 < i < N – 1, then arr[i] is the peak element.
If arr[0] > arr[1], then arr[0] is the peak element, where N is the size of the array.
If arr[N – 2] < arr[N – 1], then arr[N – 1] is the peak element, where N is the size of the array.
If more than one peak element exists in the array, then the minimum value among them needs to be printed.
Example:
1st Iteration - 8, 6, 9 are peak values.
Remove the smallest ele. Remove 6.
New arr {2, 7, 8, 5, 1, 3, 9, 4}. Output Arr - {6}
2nd Iteration - 8, 9 are peak values.
Remove the smallest ele. Remove 8.
New arr {2, 7, 5, 1, 3, 9, 4}. Output Arr - {6, 8}
3rd Iteration - 7, 9 are peak values.
Remove the smallest ele. Remove 7.
New arr {2, 5, 1, 3, 9, 4}. Output Arr - {6, 7, 8}
4th Iteration - 5, 9 are peak values.
Remove the smallest ele. Remove 5.
New arr {2, 1, 3, 9, 4}. Output Arr - {6, 7, 8, 5}
5th Iteration - 2, 9 are peak values.
Remove the smallest ele. Remove 2.
New arr {1, 3, 9, 4}. Output Arr - {6, 7, 8, 5, 2}
6th Iteration - 9 are peak values.
Remove the smallest ele. Remove 9.
New arr {1, 3, 4}. Output Arr - {6, 7, 8, 5, 2, 9}
7th Iteration - 4 are peak values.
Remove the smallest ele. Remove 4.
New arr {1, 3}. Output Arr - {6, 7, 8, 5, 2, 9, 4}
8th Iteration - 3 are peak values.
Remove the smallest ele. Remove 3.
New arr {1}. Output Arr - {6, 7, 8, 5, 2, 9, 4, 3}
9th Iteration - 1 are peak values.
Remove the smallest ele. Remove 1.
New arr {1}. Output Arr - {6, 7, 8, 5, 2, 9, 4, 3, 1}
Output: {6, 8, 7, 5, 2, 9, 4, 3, 1}
My Code:
nums = [2, 7, 8, 5, 1, 6, 3, 9, 4]
def deleteMinimalPeaks(nums):
from queue import PriorityQueue as pq
if len(nums) <= 1:
return nums
peakq = pq()
out = []
n = len(nums)
ridx = [i+1 for i in range(n)]
lidx = [i-1 for i in range(n)]
nums.append(-1)
if nums[0] > nums[1]:
peakq.put((nums[0], 0))
if nums[n-1] > nums[n-2]:
peakq.put((nums[n-1], n-1))
for i in range(1, n-1):
if nums[i] > nums[i-1] and nums[i] > nums[i+1]:
peakq.put((nums[i], i))
if peakq.empty():
nums.pop()
return nums[::-1]
while(not peakq.empty()):
val, idx = peakq.get()
out.append(val)
if len(out) == len(nums):
break
if ridx[idx] <= n-1:
lidx[ridx[idx]] = lidx[idx]
if lidx[idx] >= 0:
ridx[lidx[idx]] = ridx[idx]
if ridx[idx] <= n-1 \
and nums[ridx[idx]] > nums[ridx[ridx[idx]]] \
and nums[ridx[idx]] > nums[lidx[ridx[idx]]]:
peakq.put( (nums[ridx[idx]], ridx[idx]) )
if lidx[idx] >= 0 \
and nums[lidx[idx]] > nums[lidx[lidx[idx]]] \
and nums[lidx[idx]] > nums[ridx[lidx[idx]]]:
peakq.put( (nums[lidx[idx]], lidx[idx]) )
return out
deleteMinimalPeaks(nums)
Question:
The code is giving the correct result.
However, is there a more pythonic way to write the innermost while loop?

I would probably use itertools to help navigate the results. Using the recipe for triplewise with some slight adaptations, we can easily iterate over multiple values in the list.
The adaptation we will make is adding buffer to the start and end of the data. This will allow us to detect if we're on the edge of the data or not.
from itertools import pairwise
def triplewise(iterable):
for (a, _), (b, c) in pairwise(pairwise([None, *iterable, None])):
yield a, b, c
nums = [2, 7, 8, 5, 1, 6, 3, 9, 4]
for a, b, c in triplewise(nums):
print(a, b, c)
None 2 7
2 7 8
...
3 9 4
9 4 None
To create a method which returns just the peaks, all you have to do is examine the middle number against its edges. We know values of None are edges.
def find_peeks(iterable):
for a, b, c in triplewise(iterable):
if (a is None or b > a) and (c is None or b > c):
yield b
nums = [2, 7, 8, 5, 1, 6, 3, 9, 4]
print(list(find_peeks(nums)))
[8, 6, 9]
To make this iterative, you could have the find_peeks method also return the index of the peek. Then you can use min to find the minimum element to remove and continue:
def find_peeks(iterable):
for i, (a, b, c) in enumerate(triplewise(iterable)):
if (a is None or b > a) and (c is None or b > c):
yield b, i
def iterative_peeks(iterable):
while iterable:
peek, index = min(find_peeks(iterable))
yield peek
del iterable[index]
nums = [2, 7, 8, 5, 1, 6, 3, 9, 4]
print(list(iterative_peeks(nums)))
[6, 8, 7, 5, 2, 9, 4, 3, 1]

Related

Return a list of Element by discarding the Sequential Occurrence of Elements

i/p 1:
test_list = [1, 1, 3, 4, 4, 4, 5,6, 6, 7, 8, 8, 6]
o/p
[3, 5, 7, 6]
Exp: Since (1 1), (4 4 4) (6 6) (8 8) are in consecutive occurrence so resultant list has no addition of 6 but for last occurrence where 8, 6 are not in multiple consecutive occurrence so 6 is valid
in last iteration
i/p 2:
test_list = [1, 1, 3, 4, 4, 4, 5,4,6, 6, 7, 8, 8, 6]
o/p
[3, 5,4, 7, 6]
** like wise for 2nd input 4,4,4 is not valid but 5,4 is valid
Any suggestion for the expected o/p?
(I am looking for bit elaborated algorithm)
You can use itertools.groupby to group adjacent identical values, then only keep values that have group length of 1.
>>> from itertools import groupby
>>> test_list = [1, 1, 3, 4, 4, 4, 5,6, 6, 7, 8, 8, 6]
>>> [k for k, g in groupby(test_list) if len(list(g)) == 1]
[3, 5, 7, 6]
>>> test_list = [1, 1, 3, 4, 4, 4, 5,4,6, 6, 7, 8, 8, 6]
>>> [k for k, g in groupby(test_list) if len(list(g)) == 1]
[3, 5, 4, 7, 6]
First of all, you need to know that increasing i in your for loop does not change the value of i.
You can check it by runin this code:
for i in range(5):
print(i)
i = 2
This code will print 0 1 2 3 4 not 0 2 2 2 2 as you might think.
Going back to your question. I would use groupby from itertools, but since you specified you don't want to use it, I would do something like this:
if test_list[0] != test_list[1]: # <-- check if first element should belong to result
res_list.append(test_list[0])
for i in range(len(test_list[1:-1])): # Here we use input list, but without first and last element.
if test_list[i+1] == test_list[i+2] or test_list[i+1] == test_list[i]:
continue
else:
res_list.append(test_list[i+1])
if test_list[-2] != test_list[-1]: # <-- check if last element should belong to result
res_list.append(test_list[-1])

Constructive Insertion Sort

I having trouble with insertion sort and I feel I might be missing the point of the sort and misunderstanding the fundamentals.
We were given a insertion sort which edited the array which was fed into it. We are tasked with then modifying the code we have been given to then create a constructive insertion sort which will not edit the original array
This is the code I have so far
def append(A, x):
return A + [x]
def insertionSort(A):
B = []
B = append(B, A[0])
for i in range(1, len(A)):
B = insert(B, A[i], i, A)
return str(B)
def insert(B, k, hi, A):
print(B, k, hi)
for x in range(hi-1, -1, -1):
if B[x] <= k:
B = append(B, k)
return B
B = append(B, A[x])
B[0] = k
return B
print(insertionSort([2,4,6,8,10,1,3,5,7,9]))
However after the third or forth element in the list it begins adding all the items to the end of the list in reverse order
[2] 4 1
[2, 4] 6 2
[2, 4, 6] 8 3
[2, 4, 6, 8] 10 4
[2, 4, 6, 8, 10] 1 5
[1, 4, 6, 8, 10, 10, 8, 6, 4, 2] 3 6
[1, 4, 6, 8, 10, 10, 8, 6, 4, 2, 1, 10, 8, 6, 4, 3] 5 7
[1, 4, 6, 8, 10, 10, 8, 6, 4, 2, 1, 10, 8, 6, 4, 3, 3, 1, 10, 8, 6, 5] 7 8
[1, 4, 6, 8, 10, 10, 8, 6, 4, 2, 1, 10, 8, 6, 4, 3, 3, 1, 10, 8, 6, 5, 7] 9 9
[1, 4, 6, 8, 10, 10, 8, 6, 4, 2, 1, 10, 8, 6, 4, 3, 3, 1, 10, 8, 6, 5, 7, 9]
I cannot wrap my head around why this is wrong.
Thanks dearly to anyone who can help.
The reverse problem is at the foor loop in the insert function
when your loop hit those values it starts the reverse mode
def insert(B, k, hi, A):
# when hi=5
for x in range(hi-1, -1, -1):
# x = 4
# here B[4] is 10 and k=1 so B[4] <= 1 is False
# you program does not execute the inside of if
# instead it jumps to B = append(B, A[x]) where A[4] == 10
# and the this loop goes in reverse mode from 4 to 0
# when x = 3
# B[x] = 8 so 8 is not less or equal of k where k = 1
# so it jumps again to B = append(B, A[x]) where A[x] = A[3] = 8
# so it append 8
# and so on
# when this loop is finished your list will look like [1,4,6,8,10,10,8,6,4,2]
# the 1 gets added when the loop is finished at B[0] = k
# and then rest of the outputs are result of the loop inside the insertionSort func
if B[x] <= k:
B = append(B, k)
return B
B = append(B, A[x])
B[0] = k
return B
Here is a solution:
def insertionSort(A):
copy_sort = A.copy()
for i in range(1, len(copy_sort)):
item = copy_sort[i]
j = i - 1
while j >= 0 and copy_sort[j] > item:
copy_sort[j + 1] = copy_sort[j]
j -= 1
copy_sort[j + 1] = item
return copy_sort
your_array = [2,4,6,8,10,1,3,5,7,9]
sorted = insertionSort(your_array)
print(your_array)
print(sorted)
You need to work out your algorithm on paper, and then translate those steps to Python code. What you've implemented is convoluted and incorrect.
Most of all, insert is very confused as to the information it needs and how it should do its job. As best I can see from your code, you want this routine to insert a given value k into the appropriate location in list B. For some reason, you've also passed in list A and the value's location in that list, neither of which is applicable.
What your routine does then is strange; starting from the end of B (using i instead of B itself), the code checks the elements of B; every time it finds a value in the list less than the new one, it appends the new one to the end of B. Regardless of that comparison, it appends the corresponding element of A to B.
Nowhere do you insert the element in the proper place.
Rewrite this code. Start with the minimum necessary information:
def insert(arr, new_val):
# insert new_val into the list arr
Now, your function has two steps to carry out:
Find the proper position for new_val
Make a new list with the value inserted into that spot.
You return that new list.
Can you move on from there?

How to merge k sorted arrays, solution didn't work allowing duplicates.!

Given k sorted arrays of size n each, merge them and print the sorted output.
The algorithm I followed is
iterate of over each array
pick the ith index in k arrays
insert() in minheap
delMin() and append result array.
from heapq import heappop, heappush
def merge_k_arrays(list_of_lists):
result = [] #len(list_of_lists[0])*len(list_of_lists)
minHeap= []
n, k=0,0
print(list_of_lists)
while n < len(list_of_lists[0]):
if n ==0:# initial k size heap ready
while k < len(list_of_lists):
element= list_of_lists[k][n]
heappush(minHeap ,element )
k+=1
result.append(heappop(minHeap))
else: # one at a time.
k =0
while k < len(list_of_lists):
element = list_of_lists[k][n]
heappush(minHeap , element)
result.append(heappop(minHeap))
k+=1
n += 1
# add the left overs in the heap
while minHeap:
result.append(heappop(minHeap))
return result
Input:
input = [ [1, 3, 5, 7],
[2, 4, 6, 8],
[0, 9, 10, 11],
]
Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
input:
input = [ [1, 3, 5, 7],
[2, 4, 6, 8],
[3, 3, 3, 3],
[7, 7, 7,7]
]
output:
[0, 1, 2, 3, 3, 3, 4, 5, 6, 3, 7, 7, 7, 7, 3, 7, 8, 9, 10, 11]
Could anyone help me know what piece is missing from my algorithm in order to merge the duplicate arrays in the second input too?
from heapq import *
def mergeSort (arr):
n = len(arr)
minHeap = []
for i in range(n):
heappush(minHeap, (arr[i][0], i, arr[i]))
print(minHeap)
result = []
while len(minHeap) > 0:
num, ind, arr = heappop(minHeap)
result.append(num)
if len(arr) > ind + 1:
heappush(minHeap, (arr[ind+1], ind+1, arr))
return result
input = [ [1, 3, 5, 7],
[2, 4, 6, 8],
[0, 9, 10, 11],
[-100]
]
print(mergeSort(input))
To fix your code, move the result.append(heappop(minHeap)) in your second nested while loop to the outside of the nested while loop, like in your first nested while loop. This will make your code work.
else: # one at a time.
k =0
while k < len(list_of_lists):
element = list_of_lists[k][n]
heappush(minHeap , element)
k+=1
result.append(heappop(minHeap))
n += 1
If you have any space constraints, this is still problematic since you are adding nearly your entire input into the heap. If space is not an issue, there is a much cleaner way to write your solution:
def merge(A):
result = []
heap = [e for row in A for e in row]
heapify(heap)
for i in range(len(heap)):
result.append(heappop(heap))
return result
Otherwise, you will need to use a smarter solution that only allows the heap to have k elements in total, with one element from each list, and the new element you add each step should come from the origin list of the element that was just popped.

percent capture value using if-else list condition

For the sorted list l = [1,1,1,1,1,2,2,2,3]
A 75% "threshold" is defined as pct_value = 0.75*sum(l)
I want to find the value at which the threshold is reached. In this given list, since cumsum(l) = array([ 1, 2, 3, 4, 5, 7, 9, 11, 14]), the threshond pct_value is crossed at the value l[-2]. I want to write a program that finds this value at which the threshold is reached.
My if-else condition isn't correct:
pct_value = 10.5
[i+i if i+i < pct_value else i for i in L]
Any suggestion would be appreciative to fix the condition.
So is this what you want?
def find_element(L):
pct = 0.75 * sum(L)
s = 0
for i, element in enumerate(L):
if s > pct:
break
else:
s += element
return L[i]
You can use a while loop to remove the trailing elements from a copy until you hit your target. This returns a copy of the list elements that satisfy the stated condition:
li=[ 1, 2, 3, 4, 5, 7, 9, 11, 14]
li_c=li[:]
while sum(li_c)>=sum(li)*.75:
li_c.pop()
print li_c
# [1, 2, 3, 4, 5, 7, 9]
To make that more efficient, you would use a deque:
from collections import deque
li_c=deque(li)
tgt=sum(li)*.75
while sum(li_c)>=tgt:
li_c.pop()
print li_c
# deque([1, 2, 3, 4, 5, 7, 9])
If you are just looking for the index, not a copy of the elements, you can also use a while loop to do that:
i=1
while i<len(li):
if sum(li[0:i+1])>=tgt:
break
else:
i+=1
print i, li[0:i], i-len(li)
# 7 [1, 2, 3, 4, 5, 7, 9] -2

Iterating in a closed range [a, b] in python

I want to iterate over a closed range of integers [a, b] in python, ie. iterating from a to b including both a and b.
I know the following way of doing it:
for i in range(a, b+1):
do_something(i)
For iterating in the reverse direction (ie. in the order b, b-1, b-2, ..., a), I do the following:
for i in range(b, a-1, -1):
do_something(i)
I don't like this addition (b+1 in the example) and subtraction (a-1 in the example) to reach the closed end of the range. I find it less readable than the c/c++/Java counterpart (usage of <= in a loop).
Do you have something in python which can be used to iterate between the closed ranges without manual intervention of the boundaries?
It's a simple matter to define your own function and use it:
def closed_range(start, stop, step=1):
dir = 1 if (step > 0) else -1
return range(start, stop + dir, step):
In action:
>>> list(closed_range(1, 10))
0: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(closed_range(1, 10, 2))
1: [1, 3, 5, 7, 9]
>>> list(closed_range(1, 10, 3))
2: [1, 4, 7, 10]
>>> list(closed_range(10, 1, -1))
3: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> list(closed_range(10, 1, -2))
4: [10, 8, 6, 4, 2]
Save to a .py file in \PythonXX\Lib\site-packages and then you can import it for use elsewhere.

Categories

Resources