Coverage for /usr/lib/python3/dist-packages/sympy/combinatorics/subsets.py: 29%
154 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from itertools import combinations
3from sympy.combinatorics.graycode import GrayCode
6class Subset():
7 """
8 Represents a basic subset object.
10 Explanation
11 ===========
13 We generate subsets using essentially two techniques,
14 binary enumeration and lexicographic enumeration.
15 The Subset class takes two arguments, the first one
16 describes the initial subset to consider and the second
17 describes the superset.
19 Examples
20 ========
22 >>> from sympy.combinatorics import Subset
23 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
24 >>> a.next_binary().subset
25 ['b']
26 >>> a.prev_binary().subset
27 ['c']
28 """
30 _rank_binary = None
31 _rank_lex = None
32 _rank_graycode = None
33 _subset = None
34 _superset = None
36 def __new__(cls, subset, superset):
37 """
38 Default constructor.
40 It takes the ``subset`` and its ``superset`` as its parameters.
42 Examples
43 ========
45 >>> from sympy.combinatorics import Subset
46 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
47 >>> a.subset
48 ['c', 'd']
49 >>> a.superset
50 ['a', 'b', 'c', 'd']
51 >>> a.size
52 2
53 """
54 if len(subset) > len(superset):
55 raise ValueError('Invalid arguments have been provided. The '
56 'superset must be larger than the subset.')
57 for elem in subset:
58 if elem not in superset:
59 raise ValueError('The superset provided is invalid as it does '
60 'not contain the element {}'.format(elem))
61 obj = object.__new__(cls)
62 obj._subset = subset
63 obj._superset = superset
64 return obj
66 def __eq__(self, other):
67 """Return a boolean indicating whether a == b on the basis of
68 whether both objects are of the class Subset and if the values
69 of the subset and superset attributes are the same.
70 """
71 if not isinstance(other, Subset):
72 return NotImplemented
73 return self.subset == other.subset and self.superset == other.superset
75 def iterate_binary(self, k):
76 """
77 This is a helper function. It iterates over the
78 binary subsets by ``k`` steps. This variable can be
79 both positive or negative.
81 Examples
82 ========
84 >>> from sympy.combinatorics import Subset
85 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
86 >>> a.iterate_binary(-2).subset
87 ['d']
88 >>> a = Subset(['a', 'b', 'c'], ['a', 'b', 'c', 'd'])
89 >>> a.iterate_binary(2).subset
90 []
92 See Also
93 ========
95 next_binary, prev_binary
96 """
97 bin_list = Subset.bitlist_from_subset(self.subset, self.superset)
98 n = (int(''.join(bin_list), 2) + k) % 2**self.superset_size
99 bits = bin(n)[2:].rjust(self.superset_size, '0')
100 return Subset.subset_from_bitlist(self.superset, bits)
102 def next_binary(self):
103 """
104 Generates the next binary ordered subset.
106 Examples
107 ========
109 >>> from sympy.combinatorics import Subset
110 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
111 >>> a.next_binary().subset
112 ['b']
113 >>> a = Subset(['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd'])
114 >>> a.next_binary().subset
115 []
117 See Also
118 ========
120 prev_binary, iterate_binary
121 """
122 return self.iterate_binary(1)
124 def prev_binary(self):
125 """
126 Generates the previous binary ordered subset.
128 Examples
129 ========
131 >>> from sympy.combinatorics import Subset
132 >>> a = Subset([], ['a', 'b', 'c', 'd'])
133 >>> a.prev_binary().subset
134 ['a', 'b', 'c', 'd']
135 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
136 >>> a.prev_binary().subset
137 ['c']
139 See Also
140 ========
142 next_binary, iterate_binary
143 """
144 return self.iterate_binary(-1)
146 def next_lexicographic(self):
147 """
148 Generates the next lexicographically ordered subset.
150 Examples
151 ========
153 >>> from sympy.combinatorics import Subset
154 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
155 >>> a.next_lexicographic().subset
156 ['d']
157 >>> a = Subset(['d'], ['a', 'b', 'c', 'd'])
158 >>> a.next_lexicographic().subset
159 []
161 See Also
162 ========
164 prev_lexicographic
165 """
166 i = self.superset_size - 1
167 indices = Subset.subset_indices(self.subset, self.superset)
169 if i in indices:
170 if i - 1 in indices:
171 indices.remove(i - 1)
172 else:
173 indices.remove(i)
174 i = i - 1
175 while i >= 0 and i not in indices:
176 i = i - 1
177 if i >= 0:
178 indices.remove(i)
179 indices.append(i+1)
180 else:
181 while i not in indices and i >= 0:
182 i = i - 1
183 indices.append(i + 1)
185 ret_set = []
186 super_set = self.superset
187 for i in indices:
188 ret_set.append(super_set[i])
189 return Subset(ret_set, super_set)
191 def prev_lexicographic(self):
192 """
193 Generates the previous lexicographically ordered subset.
195 Examples
196 ========
198 >>> from sympy.combinatorics import Subset
199 >>> a = Subset([], ['a', 'b', 'c', 'd'])
200 >>> a.prev_lexicographic().subset
201 ['d']
202 >>> a = Subset(['c','d'], ['a', 'b', 'c', 'd'])
203 >>> a.prev_lexicographic().subset
204 ['c']
206 See Also
207 ========
209 next_lexicographic
210 """
211 i = self.superset_size - 1
212 indices = Subset.subset_indices(self.subset, self.superset)
214 while i >= 0 and i not in indices:
215 i = i - 1
217 if i == 0 or i - 1 in indices:
218 indices.remove(i)
219 else:
220 if i >= 0:
221 indices.remove(i)
222 indices.append(i - 1)
223 indices.append(self.superset_size - 1)
225 ret_set = []
226 super_set = self.superset
227 for i in indices:
228 ret_set.append(super_set[i])
229 return Subset(ret_set, super_set)
231 def iterate_graycode(self, k):
232 """
233 Helper function used for prev_gray and next_gray.
234 It performs ``k`` step overs to get the respective Gray codes.
236 Examples
237 ========
239 >>> from sympy.combinatorics import Subset
240 >>> a = Subset([1, 2, 3], [1, 2, 3, 4])
241 >>> a.iterate_graycode(3).subset
242 [1, 4]
243 >>> a.iterate_graycode(-2).subset
244 [1, 2, 4]
246 See Also
247 ========
249 next_gray, prev_gray
250 """
251 unranked_code = GrayCode.unrank(self.superset_size,
252 (self.rank_gray + k) % self.cardinality)
253 return Subset.subset_from_bitlist(self.superset,
254 unranked_code)
256 def next_gray(self):
257 """
258 Generates the next Gray code ordered subset.
260 Examples
261 ========
263 >>> from sympy.combinatorics import Subset
264 >>> a = Subset([1, 2, 3], [1, 2, 3, 4])
265 >>> a.next_gray().subset
266 [1, 3]
268 See Also
269 ========
271 iterate_graycode, prev_gray
272 """
273 return self.iterate_graycode(1)
275 def prev_gray(self):
276 """
277 Generates the previous Gray code ordered subset.
279 Examples
280 ========
282 >>> from sympy.combinatorics import Subset
283 >>> a = Subset([2, 3, 4], [1, 2, 3, 4, 5])
284 >>> a.prev_gray().subset
285 [2, 3, 4, 5]
287 See Also
288 ========
290 iterate_graycode, next_gray
291 """
292 return self.iterate_graycode(-1)
294 @property
295 def rank_binary(self):
296 """
297 Computes the binary ordered rank.
299 Examples
300 ========
302 >>> from sympy.combinatorics import Subset
303 >>> a = Subset([], ['a','b','c','d'])
304 >>> a.rank_binary
305 0
306 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
307 >>> a.rank_binary
308 3
310 See Also
311 ========
313 iterate_binary, unrank_binary
314 """
315 if self._rank_binary is None:
316 self._rank_binary = int("".join(
317 Subset.bitlist_from_subset(self.subset,
318 self.superset)), 2)
319 return self._rank_binary
321 @property
322 def rank_lexicographic(self):
323 """
324 Computes the lexicographic ranking of the subset.
326 Examples
327 ========
329 >>> from sympy.combinatorics import Subset
330 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
331 >>> a.rank_lexicographic
332 14
333 >>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
334 >>> a.rank_lexicographic
335 43
336 """
337 if self._rank_lex is None:
338 def _ranklex(self, subset_index, i, n):
339 if subset_index == [] or i > n:
340 return 0
341 if i in subset_index:
342 subset_index.remove(i)
343 return 1 + _ranklex(self, subset_index, i + 1, n)
344 return 2**(n - i - 1) + _ranklex(self, subset_index, i + 1, n)
345 indices = Subset.subset_indices(self.subset, self.superset)
346 self._rank_lex = _ranklex(self, indices, 0, self.superset_size)
347 return self._rank_lex
349 @property
350 def rank_gray(self):
351 """
352 Computes the Gray code ranking of the subset.
354 Examples
355 ========
357 >>> from sympy.combinatorics import Subset
358 >>> a = Subset(['c','d'], ['a','b','c','d'])
359 >>> a.rank_gray
360 2
361 >>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
362 >>> a.rank_gray
363 27
365 See Also
366 ========
368 iterate_graycode, unrank_gray
369 """
370 if self._rank_graycode is None:
371 bits = Subset.bitlist_from_subset(self.subset, self.superset)
372 self._rank_graycode = GrayCode(len(bits), start=bits).rank
373 return self._rank_graycode
375 @property
376 def subset(self):
377 """
378 Gets the subset represented by the current instance.
380 Examples
381 ========
383 >>> from sympy.combinatorics import Subset
384 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
385 >>> a.subset
386 ['c', 'd']
388 See Also
389 ========
391 superset, size, superset_size, cardinality
392 """
393 return self._subset
395 @property
396 def size(self):
397 """
398 Gets the size of the subset.
400 Examples
401 ========
403 >>> from sympy.combinatorics import Subset
404 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
405 >>> a.size
406 2
408 See Also
409 ========
411 subset, superset, superset_size, cardinality
412 """
413 return len(self.subset)
415 @property
416 def superset(self):
417 """
418 Gets the superset of the subset.
420 Examples
421 ========
423 >>> from sympy.combinatorics import Subset
424 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
425 >>> a.superset
426 ['a', 'b', 'c', 'd']
428 See Also
429 ========
431 subset, size, superset_size, cardinality
432 """
433 return self._superset
435 @property
436 def superset_size(self):
437 """
438 Returns the size of the superset.
440 Examples
441 ========
443 >>> from sympy.combinatorics import Subset
444 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
445 >>> a.superset_size
446 4
448 See Also
449 ========
451 subset, superset, size, cardinality
452 """
453 return len(self.superset)
455 @property
456 def cardinality(self):
457 """
458 Returns the number of all possible subsets.
460 Examples
461 ========
463 >>> from sympy.combinatorics import Subset
464 >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
465 >>> a.cardinality
466 16
468 See Also
469 ========
471 subset, superset, size, superset_size
472 """
473 return 2**(self.superset_size)
475 @classmethod
476 def subset_from_bitlist(self, super_set, bitlist):
477 """
478 Gets the subset defined by the bitlist.
480 Examples
481 ========
483 >>> from sympy.combinatorics import Subset
484 >>> Subset.subset_from_bitlist(['a', 'b', 'c', 'd'], '0011').subset
485 ['c', 'd']
487 See Also
488 ========
490 bitlist_from_subset
491 """
492 if len(super_set) != len(bitlist):
493 raise ValueError("The sizes of the lists are not equal")
494 ret_set = []
495 for i in range(len(bitlist)):
496 if bitlist[i] == '1':
497 ret_set.append(super_set[i])
498 return Subset(ret_set, super_set)
500 @classmethod
501 def bitlist_from_subset(self, subset, superset):
502 """
503 Gets the bitlist corresponding to a subset.
505 Examples
506 ========
508 >>> from sympy.combinatorics import Subset
509 >>> Subset.bitlist_from_subset(['c', 'd'], ['a', 'b', 'c', 'd'])
510 '0011'
512 See Also
513 ========
515 subset_from_bitlist
516 """
517 bitlist = ['0'] * len(superset)
518 if isinstance(subset, Subset):
519 subset = subset.subset
520 for i in Subset.subset_indices(subset, superset):
521 bitlist[i] = '1'
522 return ''.join(bitlist)
524 @classmethod
525 def unrank_binary(self, rank, superset):
526 """
527 Gets the binary ordered subset of the specified rank.
529 Examples
530 ========
532 >>> from sympy.combinatorics import Subset
533 >>> Subset.unrank_binary(4, ['a', 'b', 'c', 'd']).subset
534 ['b']
536 See Also
537 ========
539 iterate_binary, rank_binary
540 """
541 bits = bin(rank)[2:].rjust(len(superset), '0')
542 return Subset.subset_from_bitlist(superset, bits)
544 @classmethod
545 def unrank_gray(self, rank, superset):
546 """
547 Gets the Gray code ordered subset of the specified rank.
549 Examples
550 ========
552 >>> from sympy.combinatorics import Subset
553 >>> Subset.unrank_gray(4, ['a', 'b', 'c']).subset
554 ['a', 'b']
555 >>> Subset.unrank_gray(0, ['a', 'b', 'c']).subset
556 []
558 See Also
559 ========
561 iterate_graycode, rank_gray
562 """
563 graycode_bitlist = GrayCode.unrank(len(superset), rank)
564 return Subset.subset_from_bitlist(superset, graycode_bitlist)
566 @classmethod
567 def subset_indices(self, subset, superset):
568 """Return indices of subset in superset in a list; the list is empty
569 if all elements of ``subset`` are not in ``superset``.
571 Examples
572 ========
574 >>> from sympy.combinatorics import Subset
575 >>> superset = [1, 3, 2, 5, 4]
576 >>> Subset.subset_indices([3, 2, 1], superset)
577 [1, 2, 0]
578 >>> Subset.subset_indices([1, 6], superset)
579 []
580 >>> Subset.subset_indices([], superset)
581 []
583 """
584 a, b = superset, subset
585 sb = set(b)
586 d = {}
587 for i, ai in enumerate(a):
588 if ai in sb:
589 d[ai] = i
590 sb.remove(ai)
591 if not sb:
592 break
593 else:
594 return []
595 return [d[bi] for bi in b]
598def ksubsets(superset, k):
599 """
600 Finds the subsets of size ``k`` in lexicographic order.
602 This uses the itertools generator.
604 Examples
605 ========
607 >>> from sympy.combinatorics.subsets import ksubsets
608 >>> list(ksubsets([1, 2, 3], 2))
609 [(1, 2), (1, 3), (2, 3)]
610 >>> list(ksubsets([1, 2, 3, 4, 5], 2))
611 [(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), \
612 (2, 5), (3, 4), (3, 5), (4, 5)]
614 See Also
615 ========
617 Subset
618 """
619 return combinations(superset, k)