Lists: using better slicing to find iterative sums of subsets - python

Say I have a list,
A = range(1, 6) = [1, 2, 3, 4, 5]
B, the end result, is a list of lists. Given i and j, how would you make a list of iterative sums where index i bounds one side and j the other?
B[j] = sum(A[j:i+1] or A[i:j+1]) depending on whether j or i is larger.
Examples for indices 0 and 2:
B[0] = [1, 1+2, 1+2+3, 1+2+3+4, 1+2+3+4+5]
= [1, 3, 6, 10, 15]
B[2] = [1+2+3, 2+3, 3, 3+4, 3+4+5]
= [6, 5, 3, 7, 12]
======
Current code (works) is two for loops, very brute force. I think there should be a way to use reduce?
A = range(1,6)
n = len(A)
B = []
for j in xrange(n):
b = []
for i in xrange(n):
if j <= i:
b.append(sum(A[j:i+1]))
else:
b.append(sum(A[i:j+1]))
B.append(b)
# print
for b in B:
print b
Minor context: possibly part of my solution to project euler 82

You end up recalculating the sums many times. Instead create them once and look them up for each element of b:
A = range(1,6)
n = len(A)
mapping = {}
for i in xrange(n):
for j in xrange(i,n):
mapping[i,j] = sum(A[i:j+1])
B = []
for j in xrange(n):
b = []
for i in xrange(n):
if j <= i:
b.append(mapping[j,i])
else:
b.append(mapping[i,j])
B.append(b)
you could eliminate the need to check j<=i if you just make the mapping work for both [i,j] or [j,i]:
mapping = {}
A = range(1,6)
n = len(A)
for i in xrange(n):
for j in xrange(i,n):
mapping[i,j] = sum(A[i:j+1])
mapping[j,i] = mapping[i,j] #for both ways
B = [[mapping[i,j] for i in xrange(n)] for j in xrange(n)]
Although notice that this means that every B[x][y] will directly coordinate to mapping[x,y] so you may just want to use the mapping by itself.

Related

Implementing Merge Sort algorithm

def merge(arr,l,m,h):
lis = []
l1 = arr[l:m]
l2 = arr[m+1:h]
while((len(l1) and len(l2)) is not 0):
if l1[0]<=l2[0]:
x = l1.pop(0)
else:
x = l2.pop(0)
lis.append(x)
return lis
def merge_sort(arr,l,h): generating them
if l<h:
mid = (l+h)//2
merge_sort(arr,l,mid)
merge_sort(arr,mid+1,h)
arr = merge(arr,l,mid,h)
return arr
arr = [9,3,7,5,6,4,8,2]
print(merge_sort(arr,0,7))
Can anyone please enlighten where my approach is going wrong ?
I get only [6,4,8] as the answer. I'm trying to understand the algo and implement the logic my own way. Please help.
Several issues:
As you consider h to be the last index of the sublist, then realise that when slicing a list, the second index is the one following the intended range. So change this:
Wrong
Right
l1 = arr[l:m]
l1 = arr[l:m+1]
l2 = arr[m+1:h]
l2 = arr[m+1:h+1]
As merge returns the result for a sub list, you should not assign it to arr. arr is supposed to be the total list, so you should only replace a part of it:
arr[l:h+1] = merge(arr,l,mid,h)
As the while loop requires that both lists are not empty, you should still consider the case where after the loop one of the lists is still not empty: its elements should be added to the merged result. So replace the return statement to this:
return lis + l1 + l2
It is not advised to compare integers with is or is not, which you do in the while condition. In fact that condition can be simplified to this:
while l1 and l2:
With these changes (and correct indentation) it will work.
Further remarks:
This implementation is not efficient. pop(0) has a O(n) time complexity. Use indexes that you update during the loop, instead of really extracting the values out the lists.
It is more pythonic to let h and m be the indices after the range that they close, instead of them being the indices of the last elements within the range they close. So if you go that way, then some of the above points will be resolved differently.
Corrected implementation
Here is your code adapted using all of the above remarks:
def merge(arr, l, m, h):
lis = []
i = l
j = m
while i < m and j < h:
if arr[i] <= arr[j]:
x = arr[i]
i += 1
else:
x = arr[j]
j += 1
lis.append(x)
return lis + arr[i:m] + arr[j:h]
def merge_sort(arr, l, h):
if l < h - 1:
mid = (l + h) // 2
merge_sort(arr, l, mid)
merge_sort(arr, mid, h)
arr[l:h] = merge(arr, l, mid, h)
return arr
arr = [9, 3, 7, 5, 6, 4, 8, 2]
print(merge_sort(arr,0,len(arr)))

Intersections of intervals

I understand that start[i] (first list) & end[i](second list) are creating a new output as new list but how to iterate through the twolist with a for loop or comprehension list? Can someone provide a more efficient code?
Here is the scenario:
You are given two lists of closed intervals, firstList and secondList, where firstList[i] = [starti, endi] and secondList[j] = [startj, endj]. Each list of intervals is pairwise disjoint and in sorted order.
Return the intersection of these two interval lists.
A closed interval [a, b] (with a <= b) denotes the set of real numbers x with a <= x <= b.
The intersection of two closed intervals is a set of real numbers that are either empty or represented as a closed interval. For example, the intersection of [1, 3] and [2, 4] is [2, 3].
Example 1:
  Input:
firstList = [[0,2],[5,10],[13,23],[24,25]]
secondList = [[1,5],[8,12],[15,24],[25,26]]
  Output:
[[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
Example 2:
  Input:
firstList = [[1,3],[5,9]]
secondList = []
  Output:
[]
This is what I have but I want less lines of code:
l = intervals[0][0]
r = intervals[0][1]
for i in range(1,N):
# If no intersection exists
if (intervals[i][0] > r or intervals[i][1] < l):
print(-1)
# Else update the intersection
else:
l = max(l, intervals[i][0])
r = min(r, intervals[i][1])
ended up with this:
what do you think?
def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
ans = []
i = j = 0
while i < len(A) and j < len(B):
# Let's check if A[i] intersects B[j].
# lo - the startpoint of the intersection
# hi - the endpoint of the intersection
lo = max(A[i][0], B[j][0])
hi = min(A[i][1], B[j][1])
if lo <= hi:
ans.append([lo, hi])
# Remove the interval with the smallest endpoint
if A[i][1] < B[j][1]:
i += 1
else:
j += 1
return ans
intervals1 = [[0,2],[5,10],[13,23],[24,25]]
intervals2 = [[1,5],[8,12],[15,24],[25,26]]
def get_intersection(interval1, interval2):
new_min = max(interval1[0], interval2[0])
new_max = min(interval1[1], interval2[1])
return [new_min, new_max] if new_min <= new_max else None
interval_intersection = [x for x in (get_intersection(y, z) for y in intervals1 for z in intervals2) if x is not None]
print(interval_intersection)
Try it at www.mycompiler.io
This assumes that the intervals are disjoint but would work regardless of ordering. It is very slightly inefficient in that once you have found one matching intersection you don't need to keep checking all the others.
You could merge the values of the two list with a flag identifying starts (1) and ends (3) of ranges. Sort the values and then go through them accumulating the flags (+1 for starts, -1 for ends) to determine if both lists have a range covering each value:
firstList = [[0,2],[5,10],[13,23],[24,25]]
secondList = [[1,5],[8,12],[15,24],[25,26]]
breaks = sorted((n,f) for a,b in firstList+secondList for n,f in [(a,1),(b,3)])
merged,s = [[]],0
for n,flag in breaks:
s += 2-flag
if s>=2 or s==1 and flag>1: merged[-1].append(n)
if s<2 and merged[-1]: merged.append([])
merged = merged[:-1]
print(merged)
[[1, 2], [5, 5], [8, 10], [15, 23], [24, 24], [25, 25]]
Note that I'm assuming that the ranges never overlap within a given list

While loop does not seem to loop over after 1 iteration

G'day. I am new to coding and python.
My goal is to try to create a code where if the element in y reaches the next 0, all the 0 to n (before the next zero) will become n. A sample output should look like this after executing the code below:
y = [0,1,2,3,4,5,6,7,8,0,1,0,1,2,3,4,5,6,7]
# I am interating over two inputs. y_1 = y[1:] and append 0 at the end.
y_1 = [1,2,3,4,5,6,7,8,0,1,0,1,2,3,4,5,6,7,0]
expected output:
x = [8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7]
The problem I'm facing I believe comes from the while loop not looping over after [0,1,2,3,4,5,6,7,8] is deleted from the list as specified in the code below (which logically to me should loop over?) :
y = [0,1,2,3,4,5,6,7,8,0,1,0,1,2,3,4,5,6,7]
y_1 = [1,2,3,4,5,6,7,8,0,1,0,1,2,3,4,5,6,7,0]
x = []
while len(y):
for i, j in zip(y, y_1):
if i > j:
z = i
for k in range(z+1):
x.append(y[i])
del y[0:z+1]
del y_1[0:z+1]
elif i == j:
z = 0
x.append(z)
del y[z]
del y_1[z]
Any suggestion would be greatly appreciated :)
I don't know why you use del and while because you should get expected result doing
y = [0,1,2,3,4,5,6,7,8,0,1,0,1,2,3,4,5,6,7]
y_1 = y[1:] + [0]
x = []
for i, j in zip(y, y_1):
if i > j:
z = i
for k in range(z+1):
x.append(y[i])
elif i == j:
z = 0
x.append(z)
print(x)
In Python you shouldn't delete element from list which you use as for-loop because when it delete element then it moves other elements in list used as for-loop and it can give unexpected results.
If you really want to run it in some while len(y) then you should rather create new list with elements which you want to keep. Or you should duplicate list - y_duplicated = y.copy() - and delete in duplicated list and after for-loop replace original list y = y_duplicated

Generate a random derangement of a list

How can I randomly shuffle a list so that none of the elements remains in its original position?
In other words, given a list A with distinct elements, I'd like to generate a permutation B of it so that
this permutation is random
and for each n, a[n] != b[n]
e.g.
a = [1,2,3,4]
b = [4,1,2,3] # good
b = [4,2,1,3] # good
a = [1,2,3,4]
x = [2,4,3,1] # bad
I don't know the proper term for such a permutation (is it "total"?) thus having a hard time googling. The correct term appears to be "derangement".
After some research I was able to implement the "early refusal" algorithm as described e.g. in this paper [1]. It goes like this:
import random
def random_derangement(n):
while True:
v = [i for i in range(n)]
for j in range(n - 1, -1, -1):
p = random.randint(0, j)
if v[p] == j:
break
else:
v[j], v[p] = v[p], v[j]
else:
if v[0] != 0:
return tuple(v)
The idea is: we keep shuffling the array, once we find that the permutation we're working on is not valid (v[i]==i), we break and start from scratch.
A quick test shows that this algorithm generates all derangements uniformly:
N = 4
# enumerate all derangements for testing
import itertools
counter = {}
for p in itertools.permutations(range(N)):
if all(p[i] != i for i in p):
counter[p] = 0
# make M probes for each derangement
M = 5000
for _ in range(M*len(counter)):
# generate a random derangement
p = random_derangement(N)
# is it really?
assert p in counter
# ok, record it
counter[p] += 1
# the distribution looks uniform
for p, c in sorted(counter.items()):
print p, c
Results:
(1, 0, 3, 2) 4934
(1, 2, 3, 0) 4952
(1, 3, 0, 2) 4980
(2, 0, 3, 1) 5054
(2, 3, 0, 1) 5032
(2, 3, 1, 0) 5053
(3, 0, 1, 2) 4951
(3, 2, 0, 1) 5048
(3, 2, 1, 0) 4996
I choose this algorithm for simplicity, this presentation [2] briefly outlines other ideas.
References:
[1] An analysis of a simple algorithm for random derangements. Merlini, Sprugnoli, Verri. WSPC Proceedings, 2007.
[2] Generating random derangements. Martínez, Panholzer, Prodinger.
Such permutations are called derangements. In practice you can just try random permutations until hitting a derangement, their ratio approaches the inverse of 'e' as 'n' grows.
As a possible starting point, the Fisher-Yates shuffle goes like this.
def swap(xs, a, b):
xs[a], xs[b] = xs[b], xs[a]
def permute(xs):
for a in xrange(len(xs)):
b = random.choice(xrange(a, len(xs)))
swap(xs, a, b)
Perhaps this will do the trick?
def derange(xs):
for a in xrange(len(xs) - 1):
b = random.choice(xrange(a + 1, len(xs) - 1))
swap(xs, a, b)
swap(len(xs) - 1, random.choice(xrange(n - 1))
Here's the version described by Vatine:
def derange(xs):
for a in xrange(1, len(xs)):
b = random.choice(xrange(0, a))
swap(xs, a, b)
return xs
A quick statistical test:
from collections import Counter
def test(n):
derangements = (tuple(derange(range(n))) for _ in xrange(10000))
for k,v in Counter(derangements).iteritems():
print('{} {}').format(k, v)
test(4):
(1, 3, 0, 2) 1665
(2, 0, 3, 1) 1702
(3, 2, 0, 1) 1636
(1, 2, 3, 0) 1632
(3, 0, 1, 2) 1694
(2, 3, 1, 0) 1671
This does appear uniform over its range, and it has the nice property that each element has an equal chance to appear in each allowed slot.
But unfortunately it doesn't include all of the derangements. There are 9 derangements of size 4. (The formula and an example for n=4 are given on the Wikipedia article).
This should work
import random
totalrandom = False
array = [1, 2, 3, 4]
it = 0
while totalrandom == False:
it += 1
shuffledArray = sorted(array, key=lambda k: random.random())
total = 0
for i in array:
if array[i-1] != shuffledArray[i-1]: total += 1
if total == 4:
totalrandom = True
if it > 10*len(array):
print("'Total random' shuffle impossible")
exit()
print(shuffledArray)
Note the variable it which exits the code if too many iterations are called. This accounts for arrays such as [1, 1, 1] or [3]
EDIT
Turns out that if you're using this with large arrays (bigger than 15 or so), it will be CPU intensive. Using a randomly generated 100 element array and upping it to len(array)**3, it takes my Samsung Galaxy S4 a long time to solve.
EDIT 2
After about 1200 seconds (20 minutes), the program ended saying 'Total Random shuffle impossible'. For large arrays, you need a very large number of permutations... Say len(array)**10 or something.
Code:
import random, time
totalrandom = False
array = []
it = 0
for i in range(1, 100):
array.append(random.randint(1, 6))
start = time.time()
while totalrandom == False:
it += 1
shuffledArray = sorted(array, key=lambda k: random.random())
total = 0
for i in array:
if array[i-1] != shuffledArray[i-1]: total += 1
if total == 4:
totalrandom = True
if it > len(array)**3:
end = time.time()
print(end-start)
print("'Total random' shuffle impossible")
exit()
end = time.time()
print(end-start)
print(shuffledArray)
Here is a smaller one, with pythonic syntax -
import random
def derange(s):
d=s[:]
while any([a==b for a,b in zip(d,s)]):random.shuffle(d)
return d
All it does is shuffles the list until there is no element-wise match. Also, be careful that it'll run forever if a list that cannot be deranged is passed.It happens when there are duplicates. To remove duplicates simply call the function like this derange(list(set(my_list_to_be_deranged))).
import random
a=[1,2,3,4]
c=[]
i=0
while i < len(a):
while 1:
k=random.choice(a)
#print k,a[i]
if k==a[i]:
pass
else:
if k not in c:
if i==len(a)-2:
if a[len(a)-1] not in c:
if k==a[len(a)-1]:
c.append(k)
break
else:
c.append(k)
break
else:
c.append(k)
break
i=i+1
print c
A quick way is to try to shuffle your list until you reach that state. You simply try to shuffle your list until you are left with a list that satisfies your condition.
import random
import copy
def is_derangement(l_original, l_proposal):
return all([l_original[i] != item for i, item in enumerate(l_proposal)])
l_original = [1, 2, 3, 4, 5]
l_proposal = copy.copy(l_original)
while not is_derangement(l_original, l_proposal):
random.shuffle(l_proposal)
print(l_proposal)

Python QuickSort returning original input

So here is my quicksort code
def quicksort(A):
if len(A) > 1:
pivot = A[0]
L = []
E = []
R = []
for i in A:
if i < pivot:
L.append(i)
elif i == pivot:
E.append(i)
else:
R.append(i)
quicksort(L)
quicksort(R)
A = L + E + R
And the output when I run
array = [5,6,3,2,7]
print "original array" + str(array)
quicksort(array)
print "sorted array" + str(array)
Is
original array[5, 6, 3, 2, 7]
sorted array[5, 6, 3, 2, 7]
However, when I step through the function with the debugger, the last value A ever holds is [2,3,5,6,7] which is sorted, why does A not hold this after the function is executed?
You build a new list A = L + E + R, rebinding the local name. The original list is unaffected.
You need to return the new list objects:
def quicksort(A):
if len(A) > 1:
pivot = A[0]
L = []
E = []
R = []
for i in A:
if i < pivot:
L.append(i)
elif i == pivot:
E.append(i)
else:
R.append(i)
L = quicksort(L)
R = quicksort(R)
return L + E + R
return A
This returns a new list object:
>>> quicksort([5,6,3,2,7])
[2, 3, 5, 6, 7]
Alternatively, update the list in-place by using slice assignment:
def quicksort(A):
if len(A) > 1:
pivot = A[0]
L = []
E = []
R = []
for i in A:
if i < pivot:
L.append(i)
elif i == pivot:
E.append(i)
else:
R.append(i)
quicksort(L)
quicksort(R)
A[:] = L + E + R
Now the original list is altered in-place:
>>> lst = [5,6,3,2,7]
>>> quicksort(lst)
>>> lst
[2, 3, 5, 6, 7]
The problem is that the last line doesn't do what you seem to think it does:
A = L + E + R
This adds L, E, and R together into a new list, and binds that new list to the local variable A. It doesn't affect the list that A used to be bound to in any way.
What you probably wanted to do was replace the contents of the existing list that A is bound to. Like this:
A[:] = L + E + R
You ask "why does A not hold this after the function is executed?" Well, after the function is executed, A doesn't exist at all. That's the whole point of a local variable.
Of course array exists. And array and A are originally references to the same list. If you mutate that list, you get what you want. If you make A refer to a different list, you don't.

Categories

Resources