Equal step-slicing a list by keeping minimum and maximum - python

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]

Related

Finding local minima and maxima

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

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()

If/else statement with a triplet that sum to a given value

I'm writing a sum up game where two players will take turns picking a random number in the range (1,9), no repeated number allowed. So I'm struggling at
If at any point exactly three of the player's numbers sum to 15, then that player has won.
If the first player picks [7, 2, 3, 5], he will win because 7+3+5 = 15
So my question is why doesn't the program stop when first_player has inputs == 15
I want to avoid importing any libs.
Instead of generating all permutations at each step, maintain a map of what each permutation sums to, then add two branches to each branch at each move.
Think of each entry as a set of bits, ie with each permutation you either include a given entry or not, eg if the numbers are [7, 3, 2] you might store [1, 0, 1] for the combination of the 7 and the 2.
You can make a hashmap of 101->9 etc and when someone adds a 3 to it you add an entry for 1010->9 and 1011->12. As soon as you see the target you know the game is over.
So the evolution of [7, 3, 2] would be
0->0
1->7
00->0
01->3
10->7
11->10
000->0
001->2
010->3
011->5
100->7
101->9
110->10
111->12
A more efficient way would be to find only those numbers whose sum is equal to the target that is 15.
entry = [7, 5, 1, 3]
def is_sum_15(nums):
res = []
search_numbers(nums, 3, 15, 0, [], res)
return len(res) != 0
def search_numbers(nums, k, n, index, path, res):
if k < 0 or n < 0:
return
if k == 0 and n == 0:
res.append(path)
for i in range(index, len(nums)):
search_numbers(nums, k-1, n-nums[i], i+1, path+[nums[i]], res)
print(is_sum_15(entry)) # True
An inefficient but easy way is to use itertools.permutations:
>>> entry = [7, 2, 3, 5]
>>> import itertools
>>> [sum(triplet) for triplet in itertools.permutations(entry, r=3) if sum(tr]
[12, 14, 12, 15, 14, 15, 12, 14, 12, 10, 14, 10, 12, 15, 12, 10, 15, 10, 14, 15, 14, 10, 15, 10]
>>> any(sum(triplet) == 15 for triplet in itertools.permutations(entry, r=3))
True
It's inefficient, because you would be trying all permutations every time entry gets expanded with a new number.

Linear sort by swapping adjacent numbers

I've been trying to accomplish a simple linear sort that will, in this case, make a swap at every index except for when it reaches the end. Kindly help. (the while loop might be unnecessary at this point)
array = list(range(9, -1, -1))
has_flipped = True
while has_flipped:
for num in array:
if array.index(num) == (len(array) - 1):
continue
if num > array[array.index(num) + 1]:
container = array[array.index(num) + 1]
array[array.index(num) + 1] = num
num = container
has_flipped = False
has_flipped = not has_flipped
I expect a list with the numbers 0 through 9 but I instead get 9, 9, 7, 7, 5, 5, 3, 3, 1, 1.
You do not swap rightly. You never assign to array locations in right manner. As the other answerer explains...
num = container
...does not assign to an array location.
Moreover, the while loop is not required. Here is a more compact way of doing the same:
array = list(range(9, -1, -1))
ln = len(array)
for num in array:
if num > array[ln-1]:
container = array[ln-1]
array[ln-1] = num
array[array.index(num)] = container
ln -= 1
print(array)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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