Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/combinat/choose_nk.py
4079 views
1
"""
2
Low-level Combinations
3
"""
4
#*****************************************************************************
5
# Copyright (C) 2007 Mike Hansen <[email protected]>,
6
#
7
# Distributed under the terms of the GNU General Public License (GPL)
8
#
9
# This code is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
# General Public License for more details.
13
#
14
# The full text of the GPL is available at:
15
#
16
# http://www.gnu.org/licenses/
17
#*****************************************************************************
18
import sage.misc.prandom as rnd
19
from sage.rings.arith import binomial
20
from sage.misc.misc import uniq
21
from combinat import CombinatorialClass
22
23
class ChooseNK(CombinatorialClass):
24
"""
25
Low level combinatorial class of all possible choices of k elements out of
26
range(n) without repetitions.
27
28
This is a low-level combinatorial class, with a simplistic interface by
29
design. It aim at speed. It's element are returned as plain list of python
30
int.
31
32
EXAMPLES::
33
34
sage: from sage.combinat.choose_nk import ChooseNK
35
sage: c = ChooseNK(4,2)
36
sage: c.first()
37
[0, 1]
38
sage: c.list()
39
[[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
40
sage: type(c.list()[1])
41
<type 'list'>
42
sage: type(c.list()[1][1])
43
<type 'int'>
44
"""
45
def __init__(self, n, k):
46
"""
47
TESTS::
48
49
sage: from sage.combinat.choose_nk import ChooseNK
50
sage: c52 = ChooseNK(5,2)
51
sage: c52 == loads(dumps(c52))
52
True
53
"""
54
self._n = n
55
self._k = k
56
57
def __contains__(self, x):
58
"""
59
EXAMPLES::
60
61
sage: from sage.combinat.choose_nk import ChooseNK
62
sage: c52 = ChooseNK(5,2)
63
sage: [0,1] in c52
64
True
65
sage: [1,1] in c52
66
False
67
sage: [0,1,3] in c52
68
False
69
"""
70
try:
71
x = list(x)
72
except TypeError:
73
return False
74
75
r = range(len(x))
76
return all(i in r for i in x) and len(uniq(x)) == self._k
77
78
79
def cardinality(self):
80
"""
81
Returns the number of choices of k things from a list of n things.
82
83
EXAMPLES::
84
85
sage: from sage.combinat.choose_nk import ChooseNK
86
sage: ChooseNK(3,2).cardinality()
87
3
88
sage: ChooseNK(5,2).cardinality()
89
10
90
"""
91
return binomial(self._n, self._k)
92
93
def __iter__(self):
94
"""
95
An iterator for all choices of k things from range(n).
96
97
EXAMPLES::
98
99
sage: from sage.combinat.choose_nk import ChooseNK
100
sage: [c for c in ChooseNK(5,2)]
101
[[0, 1],
102
[0, 2],
103
[0, 3],
104
[0, 4],
105
[1, 2],
106
[1, 3],
107
[1, 4],
108
[2, 3],
109
[2, 4],
110
[3, 4]]
111
"""
112
k = self._k
113
n = self._n
114
dif = 1
115
if k == 0:
116
yield []
117
return
118
119
if n < 1+(k-1)*dif:
120
return
121
else:
122
subword = [ i*dif for i in range(k) ]
123
124
yield subword[:]
125
finished = False
126
127
while not finished:
128
#Find the biggest element that can be increased
129
if subword[-1] < n-1:
130
subword[-1] += 1
131
yield subword[:]
132
continue
133
134
finished = True
135
for i in reversed(range(k-1)):
136
if subword[i]+dif < subword[i+1]:
137
subword[i] += 1
138
#Reset the bigger elements
139
for j in range(1,k-i):
140
subword[i+j] = subword[i]+j*dif
141
yield subword[:]
142
finished = False
143
break
144
145
return
146
147
148
def random_element(self):
149
"""
150
Returns a random choice of k things from range(n).
151
152
EXAMPLES::
153
154
sage: from sage.combinat.choose_nk import ChooseNK
155
sage: ChooseNK(5,2).random_element()
156
[0, 2]
157
"""
158
r = rnd.sample(xrange(self._n),self._k)
159
r.sort()
160
return r
161
162
def unrank(self, r):
163
"""
164
EXAMPLES::
165
166
sage: from sage.combinat.choose_nk import ChooseNK
167
sage: c52 = ChooseNK(5,2)
168
sage: c52.list() == map(c52.unrank, range(c52.cardinality()))
169
True
170
"""
171
if r < 0 or r >= self.cardinality():
172
raise ValueError, "rank must be between 0 and %s (inclusive)"%(self.cardinality()-1)
173
return from_rank(r, self._n, self._k)
174
175
def rank(self, x):
176
"""
177
EXAMPLES::
178
179
sage: from sage.combinat.choose_nk import ChooseNK
180
sage: c52 = ChooseNK(5,2)
181
sage: range(c52.cardinality()) == map(c52.rank, c52)
182
True
183
"""
184
if len(x) != self._k:
185
return
186
187
return rank(x, self._n)
188
189
190
def rank(comb, n):
191
"""
192
Returns the rank of comb in the subsets of range(n) of size k.
193
194
The algorithm used is based on combinadics and James McCaffrey's
195
MSDN article. See: http://en.wikipedia.org/wiki/Combinadic
196
197
EXAMPLES::
198
199
sage: import sage.combinat.choose_nk as choose_nk
200
sage: choose_nk.rank([], 3)
201
0
202
sage: choose_nk.rank([0], 3)
203
0
204
sage: choose_nk.rank([1], 3)
205
1
206
sage: choose_nk.rank([2], 3)
207
2
208
sage: choose_nk.rank([0,1], 3)
209
0
210
sage: choose_nk.rank([0,2], 3)
211
1
212
sage: choose_nk.rank([1,2], 3)
213
2
214
sage: choose_nk.rank([0,1,2], 3)
215
0
216
"""
217
218
k = len(comb)
219
if k > n:
220
raise ValueError, "len(comb) must be <= n"
221
222
#Generate the combinadic from the
223
#combination
224
w = [0]*k
225
for i in range(k):
226
w[i] = (n-1) - comb[i]
227
228
#Calculate the integer that is the dual of
229
#the lexicographic index of the combination
230
r = k
231
t = 0
232
for i in range(k):
233
t += binomial(w[i],r)
234
r -= 1
235
236
return binomial(n,k)-t-1
237
238
239
240
def _comb_largest(a,b,x):
241
"""
242
Returns the largest w < a such that binomial(w,b) <= x.
243
244
EXAMPLES::
245
246
sage: from sage.combinat.choose_nk import _comb_largest
247
sage: _comb_largest(6,3,10)
248
5
249
sage: _comb_largest(6,3,5)
250
4
251
"""
252
w = a - 1
253
254
while binomial(w,b) > x:
255
w -= 1
256
257
return w
258
259
def from_rank(r, n, k):
260
"""
261
Returns the combination of rank r in the subsets of range(n) of
262
size k when listed in lexicographic order.
263
264
The algorithm used is based on combinadics and James McCaffrey's
265
MSDN article. See: http://en.wikipedia.org/wiki/Combinadic
266
267
EXAMPLES::
268
269
sage: import sage.combinat.choose_nk as choose_nk
270
sage: choose_nk.from_rank(0,3,0)
271
[]
272
sage: choose_nk.from_rank(0,3,1)
273
[0]
274
sage: choose_nk.from_rank(1,3,1)
275
[1]
276
sage: choose_nk.from_rank(2,3,1)
277
[2]
278
sage: choose_nk.from_rank(0,3,2)
279
[0, 1]
280
sage: choose_nk.from_rank(1,3,2)
281
[0, 2]
282
sage: choose_nk.from_rank(2,3,2)
283
[1, 2]
284
sage: choose_nk.from_rank(0,3,3)
285
[0, 1, 2]
286
"""
287
if k < 0:
288
raise ValueError, "k must be > 0"
289
if k > n:
290
raise ValueError, "k must be <= n"
291
292
a = n
293
b = k
294
x = binomial(n,k) - 1 - r # x is the 'dual' of m
295
comb = [None]*k
296
297
for i in range(k):
298
comb[i] = _comb_largest(a,b,x)
299
x = x - binomial(comb[i], b)
300
a = comb[i]
301
b = b -1
302
303
for i in range(k):
304
comb[i] = (n-1)-comb[i]
305
306
return comb
307
308
309