efficiently converting a bijection to cycle notation - python

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.

Related

For loop or Lambda function?

I have this two code, they both (only the first right now) should return me the lenght of sequence that is repeated (13, 6, 6, 14, 6 AND 13, 6, 6, 14, 6):
l = [13, 6, 6, 14, 6, 13, 6, 6, 14, 6]
def count_period(l):
l_max = int((len(l)/2)+1)
for i in range(2, l_max):
interval1 = l[0:i]
intervallo2 = l[i:2*i]
if interval1 == interval2:
return i
And:
l = [13, 6, 6, 14, 6, 13, 6, 6, 14, 6]
l_max = int((len(l)/2)+1)
period_value= next(filter(lambda x: [l[0:x] == l[x:2*x] in range(2, l_max)], l), None)
Why the first code return the right value (5) while the second return me 13? What I'm doing wrong? About this topic: since I'm newbie to Python from a general point of view (and not about this case), how I can know which path should i follow to get a good code (with low execution time and low cyclomatic complexity)? For example in this case (let us assume that I haven't written anything yet), which of this two way (or another) should I follow? Thank you!
You should be filtering range(2, l_max) not l to translate your code from the top into a filter. Right now for each value in l you are creating a list in the predicate lamdba you define filter to use. If that list has items in it it returns true, only the empty list would return false. Filter then decides to keep the value x if it passes the predicate or discards it if it does not. So therefore it keeps all items in the original list and next on that list is 13. You should do this instead:
l = [13, 6, 6, 14, 6, 13, 6, 6, 14, 6]
l_max = int((len(l)/2)+1)
period_value= next(filter(lambda x: l[0:x] == l[x:2*x], range(2, l_max)), None)

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.

Equal step-slicing a list by keeping minimum and maximum

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]

Why isn't my insertion sort working?

I am trying to implement an insertion sort from reading the vague description, so I may very well be going about it the wrong way. However, from what I can see this should work:
import random
arr = []
c_num = 0
for i in range(0, 10):
arr.append(random.randint(0, 10))
def InsertionSort(unsorted_array, current_num):
smallest_num = unsorted_array[current_num]
smallest_num_index = 0
isSorted = True
for i in range(current_num, len(unsorted_array) - 1):
if unsorted_array[i] < smallest_num:
smallest_num = unsorted_array[i]
smallest_num_index = i
isSorted = False
if isSorted:
return unsorted_array
else:
temp = unsorted_array[current_num]
unsorted_array[current_num] = smallest_num
unsorted_array[smallest_num_index] = temp
current_num += 1
return InsertionSort(unsorted_array, current_num)
print(InsertionSort(arr, c_num))
It will sort the first few elements, and then just print out the now slightly-sorted array, but I can't see what I am missing to get the whole thing sorted.
Sample:
input = [9, 8, 5, 4, 5, 8, 2, 10, 7, 5]
output = [2, 4, 5, 8, 5, 8, 9, 10, 7, 5]

Insertion algorithm can not insert all nodes

I am newbie in Python. I want to insert new node into the current route to check whether it makes the route shorter or not. However my code doesn't run well, please show me the mistake. The steps are following:
1. Create random subtour (example: 0-2-0)
2. Get randomly the node which is not visited and check this node in each pair of nodes in current route. If the node satisfy the shorter requirement, we insert it into current node (example: 0-4-2-0).
3. Continue until all nodes inserted into the route.
import random
distMatrix = [
[100, 14, 20, 10, 35, 18, 5],
[6, 100, 7, 35, 17, 9, 24],
[8, 35, 100, 36, 27, 3, 15],
[21, 7, 12, 100, 7, 4, 26],
[33, 25, 6, 18, 100, 19, 11],
[6, 2, 22, 30, 9, 100, 8],
[24, 3, 12, 5,17, 16, 100],
]
def get_total_distance(route,d):
total = 0
for i in range (len(route)-1):
pre = route[i]
succ = route[i+1]
total += d[pre][succ]
return total
def insertion(d):
numNodes = len(d)
notVisited = list(range(1, numNodes))
first_random_node = random.choice(notVisited)
route = [0]
route.append(first_random_node)
notVisited.remove(first_random_node)
route.append(0) #create first subtour
print("1st",route)
location = 0
while len(notVisited) != 0:
for j in notVisited:
for i in range (len(route)-1):
pre = route[i]
succ = route[i+1]
check_route = d[pre][j] + d[j][succ]
current_distance = d[pre][succ]
if check_route <= current_distance:
print(j)
route.insert(i + 1, j)
notVisited.remove(j)
print("2nd", route)
return route
solution = insertion(distMatrix)
print("The solution for the route is:",solution)
print("The total distance is:", get_total_distance(solution,distMatrix))
As it is, your code doesn't run: there's a syntax error because you named a local function the same as a package you're trying to use: random. Inside your function random, you can no longer access the package of that name, because you redefined the name.
Once that is resolved, your code hangs with this output:
1st [0, 5, 0]
3
2nd [0, 3, 5, 0]
6
2nd [0, 6, 3, 5, 0]
You have a logic problem with this combination:
while len(notVisited) != 0:
print("WHILE", len(notVisited))
for j in notVisited:
....
if check_route <= current_distance:
print(j)
route.insert(i + 1, j)
notVisited.remove(j)
print("2nd", route)
Your only exit from the while loop is to remove everything from notVisited. There's no logical guarantee that will happen -- and when it doesn't, you're stuck in an infinite loop trying to find a shorter route that doesn't exist.

Categories

Resources