-
Notifications
You must be signed in to change notification settings - Fork 0
/
perms.py
293 lines (242 loc) · 7.52 KB
/
perms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import math
import types
import pyximport
pyximport.install()
from itertools import product, permutations
from collections import Counter
from perm_by_index_optimized import permutation_by_index
# the optimized versions of the built-in permutations generator and of the permutation by index function
indexed_perm_opt = permutation_by_index
built_it_perm_opt = permutations
def indexed_perm_unopt(n, k):
"""get the permutation at index k for permutations of n players
Args:
n (int): The length of the set of players
k (int): The index of the permutation
Returns:
TYPE: tuple (with index of the elements)
"""
numbers = list(range(n))
permutation = []
k -= 1
while n > 0:
n -= 1
# get the index of current digit
index, k = divmod(k, math.factorial(n))
permutation.append(numbers.pop(index))
return tuple(permutation)
def built_it_perm_unopt(lst):
"""The pseudocode given for Python's C implementation of permutation
from: http://svn.python.org/view/python/branches/py3k/Modules/itertoolsmodule.c?view=markup
but it only works in python 2k :)"""
pool = tuple(lst)
n = len(pool)
r = n
indices = list(range(n))
cycles = list(range(n - r + 1, n + 1))[::-1]
yield tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i + 1:] + indices[i:i + 1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield tuple(pool[i] for i in indices[:r])
break
else:
return
def product_permutations(iterable):
pool = tuple(iterable)
n = len(pool)
for indices in product(range(n), repeat=n):
if len(set(indices)) == n:
yield tuple(pool[i] for i in indices)
def perm1(lst):
yield tuple(lst)
if len(lst) == 1:
return
n = len(lst) - 1
while 1:
j = n - 1
while lst[j] >= lst[j + 1]:
j -= 1
if j == -1:
return # terminate
l = n
while lst[j] >= lst[l]:
l -= 1
lst[j], lst[l] = lst[l], lst[j]
k = j + 1
l = n
while k < l:
lst[k], lst[l] = lst[l], lst[k]
k += 1
l -= 1
yield tuple(lst)
def perm2(lst):
yield tuple(lst)
if len(lst) == 1:
return
if len(lst) == 2:
lst[0], lst[1] = lst[1], lst[0]
yield tuple(lst)
return
n = len(lst) - 1
while 1:
# half the time, j = n-1, so we can just switch n-1 with n
if lst[-2] < lst[-1]:
lst[-2], lst[-1] = lst[-1], lst[-2]
else:
# and now we know that n-1 > n, so start j at n-2
j = n - 2
while lst[j] >= lst[j + 1]:
j -= 1
if j == -1:
return # terminate
l = n
while lst[j] >= lst[l]:
l -= 1
lst[j], lst[l] = lst[l], lst[j]
k = j + 1
l = n
while k < l:
lst[k], lst[l] = lst[l], lst[k]
k += 1
l -= 1
yield tuple(lst)
def perm3(lst):
yield tuple(lst)
if len(lst) == 1:
return
if len(lst) == 2:
lst[0], lst[1] = lst[1], lst[0]
yield tuple(lst)
return
n = len(lst) - 1
while 1:
# half the time, j = n-1, so we can just switch n-1 with n
if lst[-2] < lst[-1]:
lst[-2], lst[-1] = lst[-1], lst[-2]
# let's special case the j = n-2 scenario too!
elif lst[-3] < lst[-2]:
if lst[-3] < lst[-1]:
lst[-3], lst[-2], lst[-1] = lst[-1], lst[-3], lst[-2]
else:
lst[-3], lst[-2], lst[-1] = lst[-2], lst[-1], lst[-3]
else:
# and now we know that n-2 > n-1, so start j at n-3
j = n - 3
if j < 0:
return
y = lst[j]
x = lst[-3]
z = lst[-1]
while y >= x:
j -= 1
if j < 0:
return # terminate
x = y
y = lst[j]
if y < z:
lst[j] = z
lst[j + 1] = y
lst[n] = x
else:
l = n - 1
while y >= lst[l]:
l -= 1
lst[j], lst[l] = lst[l], y
lst[n], lst[j + 1] = lst[j + 1], lst[n]
k = j + 2
l = n - 1
while k < l:
lst[k], lst[l] = lst[l], lst[k]
k += 1
l -= 1
yield tuple(lst)
def perm4(lst):
if max([lst.count(x) for x in lst]) > 1:
raise "no repeated elements"
yield tuple(lst)
if len(lst) == 1:
return
n = len(lst) - 1
c = [0 for i in range(n + 1)]
o = [1 for i in range(n + 1)]
j = n
s = 0
while 1:
q = c[j] + o[j]
if q >= 0 and q != j + 1:
lst[j - c[j] + s], lst[j - q + s] = lst[j - q + s], lst[j - c[j] + s]
yield tuple(lst)
c[j] = q
j = n
s = 0
continue
elif q == j + 1:
if j == 1:
return
s += 1
o[j] = -o[j]
j -= 1
def perm5(l):
if len(l) == 1:
yield tuple(l)
return
pop, insert, append = l.pop, l.insert, l.append
def halfperm():
ll = l
llen = len(ll)
if llen == 2:
yield ll
return
aRange = range(llen)
v = pop()
for p in halfperm():
for j in aRange:
insert(j, v)
yield ll
del ll[j]
append(v)
for h in halfperm():
yield tuple(h)
h.reverse()
yield tuple(h)
h.reverse()
# making the functions returning an iterator instead of a generator (of size factorial(n))
def assert_permutations(perm_func):
n = 7
perms = perm_func(n)
if isinstance(perms, types.GeneratorType):
perms = list(perms)
# lengths check
assert all([len(perm) == n for perm in perms]), f'"{perm_func.__name__}" failed the lengths test,\n length must be {n}, first one was {len(perms[0])}'
# unique elements check
assert all([len(set(perm)) == n for perm in perms]), f'"{perm_func.__name__}" failed the unique elements test'
# unique permutation check
assert Counter(perms).most_common(1)[0][1] == 1, f'"{perm_func.__name__}" failed the unique permutations test'
# completeness check
assert len(perms) == math.factorial(n), f'"{perm_func.__name__}" failed the completeness test'
print(f'\033[1m{perm_func.__name__:20}\033[0m correctly implemented!')
def indexed_unoptimized(n):
return [indexed_perm_unopt(n, k) for k in range(1, math.factorial(n) + 1)]
def indexed_optimized(n):
return [indexed_perm_opt(n, k) for k in range(1, math.factorial(n) + 1)]
def built_in_optimized(n):
return list(built_it_perm_opt(list(range(n))))
def built_in_unoptimized(n):
return list(built_it_perm_unopt(list(range(n))))
def product_perms(n):
return list(product_permutations(range(n)))
def method1(n):
return list(perm1(list(range(n))))
def method2(n):
return list(perm2(list(range(n))))
def method3(n):
return list(perm4(list(range(n))))
def method4(n):
return list(perm5(list(range(n))))