Debug of Nuts & Bolts Problem python code - python

I tried to write the code to solve Nuts & Bolts Problem by using quicksort(?), but it does not work well. The most confusing part for me is the returning point of recursion. How can I modify it and is there any tip to considering the returning point of recursion?
Nuts & Bolts Problem:
Given a set of n nuts of different sizes and n bolts of different sizes. There is a one-one mapping between nuts and bolts. Match nuts and bolts efficiently.
Constraint: Comparison of a nut to another nut or a bolt to another bolt is not allowed. It means nut can only be compared with bolt and bolt can only be compared with nut to see which one is bigger/smaller.
Other way of asking this problem is, given a box with locks and keys where one lock can be opened by one key in the box. We need to match the pair.
def smallerThanPartition(nuts, p):
lowN = []
for i in range(0, len(nuts)):
if p > nuts[i]:
lowN.append(nuts[i])
return lowN
def largerThanPartition(nuts, p):
highN = []
for i in range(0, len(nuts)):
if p < nuts[i]:
highN.append(nuts[i])
return highN
def ALG5(nuts, bolts, partition):
if(len(bolts) <= 1):
print('nuts + bolts = '+ str(nuts + bolts))
return nuts + bolts
else:
lowB = []
highB = []
a = []
b = []
lowN = []
highN = []
for i in range(0, len(nuts)):
if nuts[partition] > bolts[i]:
lowB.append(bolts[i])
lowN = smallerThanPartition(nuts, nuts[partition])
else:
highB.append(bolts[i])
highN = largerThanPartition(nuts, nuts[partition])
a = ALG5(lowN, lowB, len(lowN) / 2)
b = ALG5(highN, highB, len(highN) / 2)
return a + nuts[partition] + b
if __name__ == "__main__":
nuts = [1, 3, 5, 2, 44, 6]
bolts = [5, 2, 6, 44, 1, 3]
n = len(nuts)
partition = n / 2
print(ALG5(nuts, bolts, partition))
the result of the above code is
[1, 1, 2, 3, 2, 5, 5, 6, 44, 44]
[1, 3, 5, 2, 44, 6]
[5, 2, 6, 44, 1, 3]
the ideal result is
[1, 2, 3, 5, 6, 44]
[1, 3, 5, 2, 44, 6]
[5, 2, 6, 44, 1, 3]

Related

Why does the runtime seems too long?

I am trying to run my code below in Jupyter notebook, but the run time seems endless and then my laptop suddenly hangs. Could you please tell me what is wrong with my code? (here, for better understanding, I made the data very smaller)
customers = [1,2,3]
nodes = [3,5,6,8,10,14,18]
edges = [(3,6),(8,18),(8,3),(8,10),(8,7),(14,3),(14,5),(14,7),(18,3),(18,8),(18,14)]
demands = {1:200, 2:300, 3:500}
origins = {1:18, 2:8, 3:14}
destinations = {1:6, 2:10, 3:5}
prio_start = {1: [3, 7, 1, 2, 5, 4, 6], 2: [6, 4, 2, 3, 7, 1, 5], 3: [1, 2, 3, 4, 6, 7, 5]}
y_set_init = []
dict_path_nodes = {}
for k in customers:
currentnode = origins[k]
path_nodes = [currentnode]
path_edges = []
prio_init = prio_start[k]
stop = False
while not stop:
for (e, g) in edges:
if e == currentnode:
max_value = max(prio_init)
max_index = prio_init.index(max_value) + 1
nextnode = max_index
path_nodes.append(nextnode)
path_edges.append((currentnode, nextnode))
if (currentnode, nextnode) not in y_set_init:
y_set_init.append((currentnode, nextnode))
currentnode = nextnode
if currentnode == destinations[k]:
stop = True
dict_path_nodes[k]= path_nodes
print(dict_path_nodes)
Is it possible that I miss some "break" for the loops?
If currentnode = 2, the loop becomes infinite (because e never equals 2). And currentnode may be 2, because it is set as an index of prios, not as they value? Please verify your logic, probably you wanted to get value of this list somewhere.

Python - iterating beginning with the middle of the list and then checking either side

Really not sure where this fits. Say, I have a list:
>>>a = [1, 2, 3, 4, 5, 6, 7]
How can I iterate it in such a way, that it will check 4 first, then 5, then 3, then 6, and then 2(and so on for bigger lists)? I have only been able to work out the middle which is
>>>middle = [len(a)/2 if len(a) % 2 = 0 else ((len(a)+1)/2)]
I'm really not sure how to apply this, nor am I sure that my way of working out the middle is the best way. I've thought of grabbing two indexes and after each iteration, adding 1 and subtracting 1 from each respective index but have no idea how to make a for loop abide by these rules.
With regards as to why I need this; it's for analysing a valid play in a card game and will check from the middle card of a given hand up to each end until a valid card can be played.
You can just keep removing from the middle of list:
lst = range(1, 8)
while lst:
print lst.pop(len(lst)/2)
This is not the best solution performance-wise (removing item from list is expensive), but it is simple - good enough for a simple game.
EDIT:
More performance stable solution would be a generator, that calculates element position:
def iter_from_middle(lst):
try:
middle = len(lst)/2
yield lst[middle]
for shift in range(1, middle+1):
# order is important!
yield lst[middle - shift]
yield lst[middle + shift]
except IndexError: # occures on lst[len(lst)] or for empty list
raise StopIteration
To begin with, here is a very useful general purpose utility to interleave two sequences:
def imerge(a, b):
for i, j in itertools.izip_longest(a,b):
yield i
if j is not None:
yield j
with that, you just need to imerge
a[len(a) / 2: ]
with
reversed(a[: len(a) / 2])
You could also play index games, for example:
>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1]
>>> a = [1, 2, 3, 4, 5, 6, 7, 8]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1, 8]
Here's a generator that yields alternating indexes for any given provided length. It could probably be improved/shorter, but it works.
def backNforth(length):
if length == 0:
return
else:
middle = length//2
yield middle
for ind in range(1, middle + 1):
if length > (2 * ind - 1):
yield middle - ind
if length > (2 * ind):
yield middle + ind
# for testing:
if __name__ == '__main__':
r = range(9)
for _ in backNforth(len(r)):
print(r[_])
Using that, you can just do this to produce a list of items in the order you want:
a = [1, 2, 3, 4, 5, 6, 7]
a_prime = [a[_] for _ in backNforth(len(a))]
In addition to the middle elements, I needed their index as well. I found Wasowski's answer very helpful, and modified it:
def iter_from_middle(lst):
index = len(lst)//2
for i in range(len(lst)):
index = index+i*(-1)**i
yield index, lst[index]
>>> my_list = [10, 11, 12, 13, 14, 15]
>>> [(index, item) for index, item in iter_from_middle(my_list)]
[(3, 13), (2, 12), (4, 14), (1, 11), (5, 15), (0, 10)]

quicksort divide-and-conquer returns incorrect partial answer

My program does not return the correct question in the end but it shows correct ones in the intermediate results. I needs help, thanks.
Output sample:
sort begin: A,start,end [3, 5, 2, 1, 7, 6, 8, 4] 0 7
sort begin: A,start,end [2, 1, 3, 5, 7, 6, 8, 4] 0 1
sort begin: A,start,end [1, 2, 3, 5, 7, 6, 8, 4] 3 7
sort begin: A,start,end [1, 2, 3, 4, 5, 6, 8, 7] 3 3
sort begin: A,start,end [1, 2, 3, 4, 5, 6, 8, 7] 5 7
sort begin: A,start,end [1, 2, 3, 4, 5, 6, 8, 7] 5 4
sort begin: A,start,end [1, 2, 3, 4, 5, 6, 8, 7] 6 7
####################################################
final result [1, 2, 3, 5, 4, 6, 8, 7]
My code:
def qSort(A,start,end):
print "sort begin: A,start,end",A,start,end
if start >= end:
return A
elif end == start + 1:
if A[start] > A[end]:
A[start],A[end] = A[end],A[start]
return A
else:
i = start + 1
j = i
p = A[start]
while j < end:
j = j + 1
if p > A[j]:
A[i],A[j] = A[j],A[i]
i = i + 1
A = A[0:start] + A[start+1:i]+ [p] + A[i:end+1]
qSort(A,start,i-2)
qSort(A,i,end)
return A
print "###################"
myarray = [3,5,2,1,7,6,8,4]
result = qSort(myarray,0,7)
print "final result",result
Sorry for my lack luster comments. I reviewed your code and realized it was correct! You have one minor coding mistake which I will point out and then explain. In your else block you currently have:
else:
# Bunch of correct stuff
# ...
# Stuff that is ALMOST correct
qSort(A,start,i-2)
qSort(A,i,end)
return A
you need to change this to:
else:
# Bunch of correct stuff
# ...
# Stuff that is definitely correct
A = qSort(A,start,i-2)
A = qSort(A,i,end)
return A
Without going too deeply into this, your function does not discriminate between list references and newly created lists. If you put print A in you elif block right before return A you will notice that on the final iteration, your sort, as is, does everything correctly, and produces the correct list!
Unfortunately, the call that produced this change was one of the lines I've mentioned above which calls the sort but doesn't store the resulting list returned by the recursive function call!
My simple change just takes the modified list returned from secondary function calls to qSort and reassigns the variable A.
Weirdly, this behavior actually worked ok for you sometimes for reasons I cannot fully explain (like the first time you enter your `elif' block which does the right thing and modifies the list correctly). I am sure someone smarter than I surely can explain the odd behavior.
Alternatively, you could come up with a simple way to count recursion depth (number of times your function calls itself) and print that out while debugging with some breakpoints in your favorite IDE.
Heres how you could do that with global variables:
recursion_depth = -1
def qSort(A,start,end):
global recursion_depth
recursion_depth += 1
print "sort begin: A,start,end,level",A,start,end,recursion_depth
# bunch of code edited out for brevity
# ...
result = qSort(myarray,0,7)
print "final result",result

Iterating over parts of the Stern-Brocot tree in Python

My goal is to iterate over the pairs [a,b] a coprime to b and a+b<=n. For example, if n=8, I want to iterate over [1, 2], [2, 3], [3, 4], [3, 5], [1, 3], [2, 5], [1, 4], [1, 5], [1, 6], [1, 7].
My first thought was a recursive function using the Stern-Brocot tree:
def Stern_Brocot(n,a=0,b=1,c=1,d=1):
if(a+b+c+d>n):
return 0
x=Stern_Brocot(n,a+c,b+d,c,d)
y=Stern_Brocot(n,a,b,a+c,b+d)
if(x==0):
if(y==0):
return [a+c,b+d]
else:
return [a+c]+[b+d]+y
else:
if(y==0):
return [a+c]+[b+d]+x
else:
return [a+c]+[b+d]+x+y
As expected,
>>> Stern_Brocot(8)
[1, 2, 2, 3, 3, 4, 3, 5, 1, 3, 2, 5, 1, 4, 1, 5, 1, 6, 1, 7]
And for n<=995, it works well. But suddenly at n>=996, it gives this error:
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
a=Stern_Brocot(996)
File "C:\Users\Pim\Documents\C Programmeren en Numerieke Wisk\Python\PE\PE127.py", line 35, in Stern_Brocot
y=Stern_Brocot(n,a,b,a+c,b+d)
...
File "C:\Users\Pim\Documents\C Programmeren en Numerieke Wisk\Python\PE\PE127.py", line 35, in Stern_Brocot
y=Stern_Brocot(n,a,b,a+c,b+d)
RuntimeError: maximum recursion depth exceeded in comparison
And since I want n to equal 120000, this approach won't work.
So my question is: what would be a good approach to iterate over parts of the Stern_Brocot tree? (if there's another way to iterate over coprime integers, that'd be good as well).
Here's an non-recursive implementation
def Stern_Brocot(n):
states = [(0, 1, 1, 1)]
result = []
while len(states) != 0:
a, b, c, d = states.pop()
if a + b + c + d <= n:
result.append((a+c, b+d))
states.append((a, b, a+c, b+d))
states.append((a+c, b+d, c, d))
return result
Before defining Stern_Brocot, add sys.setrecursionlimit(120000). This will set the program's recursion limit to 120000.
So, instead, you can do this:
import sys
sys.setrecursionlimit(120000)
def Stern_Brocot(n,a=0,b=1,c=1,d=1):
if(a+b+c+d>n):
return 0
x=Stern_Brocot(n,a+c,b+d,c,d)
y=Stern_Brocot(n,a,b,a+c,b+d)
if(x==0):
if(y==0):
return [a+c,b+d]
else:
return [a+c]+[b+d]+y
else:
if(y==0):
return [a+c]+[b+d]+x
else:
return [a+c]+[b+d]+x+y

counting up and then down a range in python

I am trying to program a standard snake draft, where team A pick, team B, team C, team C, team B, team A, ad nauseum.
If pick number 13 (or pick number x) just happened how can I figure which team picks next for n number of teams.
I have something like:
def slot(n,x):
direction = 'down' if (int(x/n) & 1) else 'up'
spot = (x % n) + 1
slot = spot if direction == 'up' else ((n+1) - spot)
return slot
I have feeling there is a simpler, more pythonic what than this solution. Anyone care to take a hack at it?
So I played around a little more. I am looking for the return of a single value, rather than the best way to count over a looped list. The most literal answer might be:
def slot(n, x): # 0.15757 sec for 100,000x
number_range = range(1, n+1) + range(n,0, -1)
index = x % (n*2)
return number_range[index]
This creates a list [1,2,3,4,4,3,2,1], figures out the index (e.g. 13 % (4*2) = 5), and then returns the index value from the list (e.g. 4). The longer the list, the slower the function.
We can use some logic to cut the list making in half. If we are counting up (i.e. (int(x/n) & 1) returns False), we get the obvious index value (x % n), else we subtract that value from n+1:
def slot(n, x): # 0.11982 sec for 100,000x
number_range = range(1, n+1) + range(n,0, -1)
index = ((n-1) - (x % n)) if (int(x/n) & 1) else (x % n)
return number_range[index]
Still avoiding a list altogether is fastest:
def slot(n, x): # 0.07275 sec for 100,000x
spot = (x % n) + 1
slot = ((n+1) - spot) if (int(x/n) & 1) else spot
return slot
And if I hold the list as variable rather than spawning one:
number_list = [1,2,3,4,5,6,7,8,9,10,11,12,12,11,10,9,8,7,6,5,4,3,2,1]
def slot(n, x): # 0.03638 sec for 100,000x
return number_list[x % (n*2)]
Why not use itertools cycle function:
from itertools import cycle
li = range(1, n+1) + range(n, 0, -1) # e.g. [1, 2, 3, 4, 4, 3, 2, 1]
it = cycle(li)
[next(it) for _ in xrange(10)] # [1, 2, 3, 4, 4, 3, 2, 1, 1, 2]
Note: previously I had answered how to run up and down, as follows:
it = cycle(range(1, n+1) + range(n, 0, -1)) #e.g. [1, 2, 3, 4, 3, 2, 1, 2, 3, ...]
Here's a generator that will fulfill what you want.
def draft(n):
while True:
for i in xrange(1,n+1):
yield i
for i in xrange(n,0,-1):
yield i
>>> d = draft(3)
>>> [d.next() for _ in xrange(12)]
[1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1]
from itertools import chain, cycle
def cycle_up_and_down(first, last):
up = xrange(first, last+1, 1)
down = xrange(last, first-1, -1)
return cycle(chain(up, down))
turns = cycle_up_and_down(1, 4)
print [next(turns) for n in xrange(10)] # [1, 2, 3, 4, 4, 3, 2, 1, 1, 2]
Here is a list of numbers that counts up, then down:
>>> [ -abs(5-i)+5 for i in range(0,10) ]
[0, 1, 2, 3, 4, 5, 4, 3, 2, 1]
Written out:
count_up_to = 5
for i in range( 0, count_up_to*2 ):
the_number_you_care_about = -abs(count_up_to-i) + count_up_to
# do stuff with the_number_you_care_about
Easier to read:
>>> list( range(0,5) ) + list( range( 5, 0, -1 ) )
[0, 1, 2, 3, 4, 5, 4, 3, 2, 1]
Written out:
count_up_to = 5
for i in list( range(0,5) ) + list( range(5, 0, -1) ):
# i is the number you care about
Another way:
from itertools import chain
for i in chain( range(0,5), range(5,0,-1) ):
# i is the number you care about

Categories