Related
Given a sequence a with n unique elements, I want to create a sequence b which is a randomly selected permutation of a such that there is at least a specified minimum distance d between duplicate elements of the sequence which is b appended to a.
For example, if a = [1,2,3] and d = 2, of the following permutations:
a b
[1, 2, 3] (1, 2, 3) mindist = 3
[1, 2, 3] (1, 3, 2) mindist = 2
[1, 2, 3] (2, 1, 3) mindist = 2
[1, 2, 3] (2, 3, 1) mindist = 2
[1, 2, 3] (3, 1, 2) mindist = 1
[1, 2, 3] (3, 2, 1) mindist = 1
b could only take one of the first four values since the minimum distance for the last two is 1 < d.
I wrote the following implementation:
import random
n = 10
alist = list(range(n))
blist = alist[:]
d = n//2
avail_indices = list(range(n))
for a_ind, a_val in enumerate(reversed(alist)):
min_ind = max(d - a_ind - 1, 0)
new_ind = random.choice(avail_indices[min_ind:])
avail_indices.remove(new_ind)
blist[new_ind] = a_val
print(alist, blist)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 3, 2, 8, 5, 6, 4, 0, 9, 7]
but I think this is n^2 time complexity (not completely sure). Here's a plot of the time required as n increases for d = n//2:
Is it possible to do better than this?
Yes, your implementation is O(n^2).
You can adapt the Fisher-Yates shuffle to this purpose. What you do is work from the start of an array to the end, placing the final value into place out of the remaining.
The trick is that while in a full shuffle you can place any element at the start, you can only place from an index that respects the distance condition.
Here is an implementation.
import random
def distance_permutation (orig, d):
answer = orig.copy()
for i in range(len(orig)):
choice = random.randrange(i, min(len(orig), len(orig) + i - d + 1))
if i < choice:
(answer[i], answer[choice]) = (answer[choice], answer[i])
return answer
n = 10
x = list(range(n))
print(x, distance_permutation(x, n//2))
Shift the items in the given array, by some number of times, as shown in the below examples;
array = [1, 2 ,3 , 4, 5, 6]
k1 = 2
k2 = -3
k3 = 20
test1:
cirShift(array, k1)
Result: [5, 6, 1, 2, 3, 4]
test2:
cirShift(array, k2)
Result: [4, 5, 6, 1, 2, 3]
test3:
cirShift(array, k3)
Result: [5, 6, 1, 2, 3, 4]
I have used the below to achieve the right-rotate a list by k positions;
def rightRotateByOne(A):
Fin= A[-1]
for i in reversed(range(len(A) - 1)):
A[i + 1] = A[i]
A[0] = Fin
def rightRotate(A, k):
if k < 0 or k >= len(A):
return
for i in range(k):
rightRotateByOne(A)
if __name__ == '__main__':
A = [1, 2, 3, 4, 5, 6, 7]
k = 3
rightRotate(A, k)
print(A)
As of now, able to obtain results for test1 but would like to achieve the test2 and test3
Even easier, split the array in half given the boundary, swap and glue back:
def cirShift(a, shift):
if not shift or not a:
return a
return a[-shift%len(a):] + a[:-shift%len(a)]
Courtesy of #KellyBundy, a short-circut one-liner:
def cirShift(a, shift):
return a and a[-shift%len(a):] + a[:-shift%len(a)]
I think this question may be an exercise in self learning ('how to do X using just python'), so my answer is auxiliary, but you can always use np.roll():
#test 1
import numpy as np
a = [1, 2 ,3, 4, 5, 6]
np.roll(a, 2)
gives output
[5, 6, 1, 2, 3, 4]
and
#test 2
np.roll(a, -2)
gives output
[3, 4, 5, 6, 1, 2]
Even if you give a number that is larger than the array size, it handles the overflow:
#test 3
np.roll(a, 10)
gives output
[3, 4, 5, 6, 1, 2]
Rolling also works in multiple dimension arrays and across specified axes, which is pretty neat.
def shift(l, shift_t):
r = [*l]
for n in range(abs(shift_t)):
if shift_t < 0:
r = r[1:] + [r[0]]
else:
r = [r[-1]] + r[:-1]
return r
The key is to take one item of the list and place it on the opposite side, which is essentially all that shifting is doing. If you shift negative, you put the first one at the end, and if you shift positive, you put the last one at the beginning.
I need to concatenate arrays but also merge the end of A with the start of B if they are overlapping.
[1, 2, 4] + [2, 4, 5] -> [1, 2, 4, 5]
[1, 2, 4] + [2, 5, 4] -> [1, 2, 4, 2, 5, 4]
[1, 2, 4] + [1, 2, 4, 5] -> [1, 2, 4, 5]
Note: Order of elements must be preserved, [4, 5] is not the same as [5, 4].
Note 2: The question can be understood like this too: We need the shortest possible extension of A such that the output ends with B.
Of course I can iterate over the second array and compare element-by element, but I am looking for a nice Numpy solution.
Originally misunderstood the problem. The problem, is from my understanding:
Two item suffix of A matches 2 item prefix of B:
[1, 2, 4] +
[2, 4, 5] =>
[1, 2, 4, 5]
No suffix of A matches a prefix of B:
[1, 2, 4] +
[2, 5, 4] ->
[1, 2, 4, 2, 5, 4]
Then we can use this terribly inefficient function:
def merge(A,B):
i = 0
m = 0
# Find largest suffix of A that matches the prefix of B with the same length
while i <= len(A):
if A[-i:] == B[:i] and i > m:
m = i
i += 1
return A + B[m:]
Below is a solution using NumPy. It's not ideal, since it requires a (possibly unneeded) sort, and an iteration. Both the sorting and iteration should be over a relatively small array (or even a single element).
import numpy as np
def merge(left, right):
"""Concatenating two arrays, merging the overlapping end and start of
the left and right array"""
# We can limit the search to the maximum possible overlap between
# the arrays, which is the minimum of the two lengths
l = min(len(left), len(right))
# Find all indices in `right` where the element matches the last element of `left`.
# Need to sort, since the `nonzero` documentation doesn't
# explicitly state whether the returned indices follow the order
# as in `right`
# As long as there are few matches, sorting will not be a showstopper
# Need to reverse the sorted array, to start from the back of the
# right array, work towards the front, until there is a proper match
for i in np.sort(np.nonzero(right[:l] == left[-1])[0])[::-1]:
# Check if the subarrays are equal
if np.all(left[-i-1:] == right[:i+1]):
return np.concatenate([left, right[i+1:]])
# No match
return np.concatenate([left, right])
a = np.array([1, 2, 4])
b = np.array([2, 4, 5])
c = np.array([2, 5, 4])
d = np.array([1, 2, 4, 5])
e = np.array([1, 2, 4, 2])
f = np.array([2, 4, 2, 5])
print(merge(a, b))
print(merge(a, c))
print(merge(a, d))
print(merge(e, b))
print(merge(e, f))
which yields
[1 2 4 5]
[1 2 4 2 5 4]
[1 2 4 5]
[1 2 4 2 4 5]
[1 2 4 2 5]
I have an O(n) solution, albeit without Numpy:
def merge(a, b):
n_a = len(a)
n = min(n_a, len(b))
m = 0
for i in range(1, n + 1):
if b[n - i] == a[n_a - 1 - m]:
m += 1
else:
m = 0
return a + b[m:]
You could do it like this.
def concatenate(a,b):
ret = a.copy()
for element in b:
if not element in ret:
ret.append(element)
return ret
This keeps the order in a + b formation.
Say I had a list:
lis = [4, 8, 2, 4, 6]
And I want to go through each value in the list and double it but If I come across the number 2, after I double it I should skip the next number and only double the on after. For example, in the end my list should look like this.
lis = [8, 16, 4, 4, 12]
Can this be possible with a for loop?
The algorithm boils down what number you are using to double the items in the list (1 or 2). Here is my take on this problem:
lis = [4, 8, 2, 4, 6]
def double_with_my_condition(l, doubler=2):
for i in l:
yield i * doubler
if i == 2:
doubler = 1
continue
doubler = 2
new_lis = [*double_with_my_condition(lis)]
print(new_lis)
Outputs:
[8, 16, 4, 4, 12]
I wrote out a really simple solution that should be easy to understand since it appears you are a beginner
lis = [4, 8, 2, 4, 6]
new_lis = []
i = 0
while (i < len(lis)):
new_lis.append(lis[i] * 2)
if (lis[i] == 2):
if (i+1 < len(lis)):
new_lis.append(lis[i+1])
i = i+1
i = i+1
print(new_lis)
This creates a new list, loops through the old list, appends the doubled value to the new list, skips a number if the value at the index is 2.
This will work!
Method-1:
lis = [4, 8, 2, 4, 6]
for i in range(len(lis)-1, -1, -1):
if(lis[i-1] == 2):
continue
else:
lis[i] = lis[i]*2
lis
Method-2:
lis1 = [4, 8, 2, 4, 6]
indices = [i+1 for i, x in enumerate(lis1) if x == 2] #Storing indices of next to 2
lis2 = [i*2 for i in lis1]
for i in indices:
lis2[i] = lis1[i] # replacing the previous values
print(lis2)
You can also use list comprehensions
lis = [4, 8, 2, 4, 6]
print([lis[x] if lis[x - 1] == 2 else lis[x] * 2 for x in range(len(lis))])
I am given a 1D array of numbers.
I need to go through the array adding each consecutive element to form a sum. Once this sum reaches a certain value, it forms the first element of a new array. The sum is then reset and the process repeats, thus iterating over the whole array.
For example if given:
[1, 3, 4, 5, 2, 5, 3]
and requiring the minimum sum to be 5,
the new array would be:
[8, 5, 7]
Explicity: [1 + 3 + 4, 5, 2 + 5]
I then also need to keep a record of the way the elements were combined for that particular array: I need to be to take a different array of the same length and combine the elements in the same way as above.
e.g. give the array
[1, 2, 1, 1, 3, 2, 1]
I require the output
[4, 1, 5]
Explicity: [1 + 2 + 1, 1, 3 + 2]
I have accomplished this with i loops and increment counters, but it is very ugly. The array named "record" contains the number of old elements summed to make each element of the new array i.e. [3, 1, 2]
import numpy as np
def bin(array, min_sum):
num_points = len(array)
# Create empty output.
output = list()
record = list()
i = 0
while i < num_points:
sum = 0
j = 0
while sum < min_sum:
# Break out if it reaches end of data whilst in loop.
if i+j == num_points:
break
sum += array[i+j]
j += 1
output.append(sum)
record.append(j)
i += j
# The final data point does not reach the min sum.
del output[-1]
return output
if __name__ == "__main__":
array = [1, 3, 4, 5, 2, 5, 3]
print bin(array, 5)
I would advice you to simply walk through the list. Add it to an accumulator like the_sum (do not use sum, since it is a builtin), and in case the_sum reaches a number higher than the min_sum, you add it, and reset the_sum to zero. Like:
def bin(array, min_sum):
result = []
the_sum = 0
for elem in array:
the_sum += elem
if the_sum >= min_sum:
result.append(the_sum)
the_sum = 0
return result
The lines where the accumulator is involved, are put in boldface.
I leave combining the other array the same way as an exercise, but as a hint: use an additional accumulator and zip to iterate over both arrays concurrently.
Here is a straightforward solution. which computes a list of boolean values where the value is true when accumulated element equals or exceeds the target value and calc computes an accumulation using this list.
def which(l, s):
w, a = [], 0
for e in l:
a += e
c = (a >= s)
w.append(c)
if c:
a = 0
return w
def calc(l, w):
a = 0
for (e, c) in zip(l, w):
a += e
if c:
yield a
a = 0
here is an interactive demonstration
>>> l1 = [1, 3, 4, 5, 2, 5, 3]
>>> w = which(l1, 5)
>>> w
[False, False, True, True, False, True, False]
>>> list(calc(l1, w))
[8, 5, 7]
>>> l2 = [1, 2, 1, 1, 3, 2, 1]
>>> list(calc(l2, w))
[4, 1, 5]
You can use short solutions I found out after a long struggle with flattening arrays.
For getting bounded sums use:
f = lambda a,x,j,l: 0 if j>=l else [a[i] for i in range(j,l) if sum(a[j:i])<x]
This outputs:
>>> f = lambda a,x,j,l: 0 if j>=l else [a[i] for i in range(j,l) if sum(a[j:i])< x]
>>> a= [1, 3, 4, 5, 2, 5, 3]
>>> f(a,5,0,7)
[1, 3, 4]
>>> sum(f(a,5,0,7))
8
>>> sum(f(a,5,3,7))
5
>>> sum(f(a,5,4,7))
7
>>>
To get your records use the function:
>>> y = lambda a,x,f,j,l: [] if j>=l else list(np.append(j,np.array(y(a,x,f,j+len(f(a,x,j,l)),l))))
From here, you can get both array of records and sums:
>>> listt=y(a,5,f,0,len(a))
>>> listt
[0.0, 3.0, 4.0, 6.0]
>>> [sum(f(a,5,int(listt[u]),len(a))) for u in range(0,len(listt)-1)]
[8, 5, 7]
>>>
Now, the bit of magic you can even use it as an index-conditional boundary for the second vector:
>>> b=[1, 2, 1, 1, 3, 2, 1]
>>> [sum(f(b,5,int(listt[u]),int(listt[u+1]))) for u in range(0,len(listt)-1)]
[4, 1, 5]
>>>