From f7475378d0d62f9b130b63f5af7d78c63ce189e4 Mon Sep 17 00:00:00 2001 From: larion_dev Date: Fri, 31 Mar 2017 17:41:31 +0700 Subject: [PATCH 1/5] Add Subsets[] --- mathics/builtin/combinatorial.py | 171 ++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index ffce21ffe6..90540e9c3a 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -9,7 +9,7 @@ from mathics.builtin.base import Builtin from mathics.core.expression import Expression, Integer, Symbol from mathics.builtin.arithmetic import _MPMathFunction - +from itertools import combinations class Fibonacci(Builtin): """ @@ -270,3 +270,172 @@ class RogersTanimotoDissimilarity(_BooleanDissimilarity): def _compute(self, n, c_ff, c_ft, c_tf, c_tt): r = 2 * (c_tf + c_ft) return Expression('Divide', r, c_tt + c_ff + r) + +class Subsets(Builtin): + """ +
+
'Subsets[$list$]' +
finds a list of all possible subsets of $list$. + +
'Subsets[$list$, $n$]' +
finds a list of all possible subsets containing at most $n$ elements. + +
'Subsets[$list$, {$n$}]' +
finds a list of all possible subsets containing exactly $n$ elements. + +
'Subsets[$list$, {$min$, $max$}]' +
finds a list of all possible subsets containing between $min$ and $max$ elements. + +
'Subsets[$list$, $spec$, $n$]' +
finds a list of the first $n$ possible subsets. + +
'Subsets[$list$, $spec$, {$n$}]' +
finds the $n$th possible subset. +
+ + >> Subsets[{a, b, c}] + = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} + + >> Subsets[{a, b, c}, 2] + = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}} + + >> Subsets[{a, b, c}, {2}] + = {{a, b}, {a, c}, {b, c}} + + #> Subsets[{a, b, c, d, e}, {3}, 5] + = {{a, b, c}, {a, b, d}, {a, b, e}, {a, c, d}, {a, c, e}} + + #> Subsets[{a, b, c, d, e}, {0, 5, 2}] + = {{}, {a, b}, {a, c}, {a, d}, {a, e}, {b, c}, {b, d}, {b, e}, {c, d}, {c, e}, {d, e}, {a, b, c, d}, {a, b, c, e}, {a, b, d, e}, {a, c, d, e}, {b, c, d, e}} + + #> Subsets[Range[5], All, {25}] + = {{2, 4, 5}} + + #> Subsets[{a, b, c, d}, All, {15, 1, -2}] + = {{b, c, d}, {a, b, d}, {c, d}, {b, c}, {a, c}, {d}, {b}, {}} + + #> Subsets[{}] + = {{}} + + #> Subsets[] + = Subsets[] + + #> Subsets[{a, b, c}, 2.5] + : Position 2 of Subsets[{a, b, c}, 2.5] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, 2.5] + + #> Subsets[{a, b, c}, -1] + : Position 2 of Subsets[{a, b, c}, -1] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, -1] + + #> Subsets[{a, b, c}, {3, 4, 5, 6}] + : Position 2 of Subsets[{a, b, c}, {3, 4, 5, 6}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, {3, 4, 5, 6}] + + #> Subsets[{a, b, c}, {-1, 2}] + : Position 2 of Subsets[{a, b, c}, {-1, 2}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, {-1, 2}] + + #> Subsets[{a, b, c}, All] + = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} + + #> Subsets[{a, b, c}, Infinity] + = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} + + #> Subsets[{a, b, c}, ALL] + : Position 2 of Subsets[{a, b, c}, ALL] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, ALL] + + #> Subsets[{a, b, c}, {a}] + : Position 2 of Subsets[{a, b, c}, {a}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, {a}] + + #> Subsets[{a, b, c}, {}] + : Position 2 of Subsets[{a, b, c}, {}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{a, b, c}, {}] + """ + + rules = { + 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]':'Take[Subsets[list, n], spec]' + } + messages = { + 'nninfseq': 'Position 2 of `1` must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer', + } + + def apply(self, list, evaluation): + 'Subsets[list_?ListQ]' + + return self.apply_1(list, Integer(len(list.to_python())), evaluation) + + def apply_1(self, list, n, evaluation): + 'Subsets[list_?ListQ, n_]' + + tlist = [x for x in list.leaves] + expr = Expression('Subsets', list, n) + + if not n.get_head_name() == 'System`Integer' or n.to_python() < 0 : + return evaluation.message('Subsets', 'nninfseq', expr) + + tlen = int(n.to_python()) + + nested_list = [Expression('List', *c) for i in range(0, tlen + 1) for c in combinations(tlist, i)] + + result = Expression('List', *nested_list) + + return result + + def apply_2(self, list, n, evaluation): + 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]]]' + + tlist = [x for x in list.leaves] + expr = Expression('Subsets', list, n) + + if n.get_name() == 'System`All' or n.has_form('DirectedInfinity', 1): + tlen = len(tlist) + return self.apply(list, evaluation) + + n_python = n.to_python() + n_len = len(n_python) + + if n_len > 3: + return evaluation.message('Subsets', 'nninfseq', expr) + + if n_len == 0: + return evaluation.message('Subsets', 'nninfseq', expr) + + if n_len == 1: + elem1 = n_python[0] + if elem1 < 0 or not n.leaves[0].get_head_name() == "System`Integer": + return evaluation.message('Subsets', 'nninfseq', expr) + min_n = elem1 + max_n = min_n + 1 + step_n = 1 + + if n_len == 2: + elem1 = n_python[0] + elem2 = n_python[1] + if elem1 < 0 or elem2 < 0 or not n.leaves[0].get_head_name() == "System`Integer" or not n.leaves[1].get_head_name() == "System`Integer" : + return evaluation.message('Subsets', 'nninfseq', expr) + min_n = elem1 + max_n = elem2 + 1 + step_n = 1 + + if n_len == 3: + if not n.leaves[0].get_head_name() == "System`Integer" or not n.leaves[1].get_head_name() == "System`Integer" or not n.leaves[2].get_head_name() == "System`Integer" : + return evaluation.message('Subsets', 'nninfseq', expr) + step_n = n_python[2] + if step_n > 0: + min_n = n_python[0] + max_n = n_python[1] + 1 + elif step_n < 0: + min_n = n_python[0] + max_n = n_python[1] - 1 + else: + return evaluation.message('Subsets', 'nninfseq', expr) + + nested_list = [Expression('List', *c) for i in range(min_n, max_n, step_n) for c in combinations(tlist, i)] + + result = Expression('List', *nested_list) + + return result + From 6c9f3e171edb127c710b76292921e514b4d1c9aa Mon Sep 17 00:00:00 2001 From: larion_dev Date: Mon, 3 Apr 2017 13:54:09 +0700 Subject: [PATCH 2/5] Fix PR#685 --- mathics/builtin/combinatorial.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index 90540e9c3a..7c08c77253 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -405,7 +405,7 @@ def apply_2(self, list, n, evaluation): if n_len == 1: elem1 = n_python[0] - if elem1 < 0 or not n.leaves[0].get_head_name() == "System`Integer": + if not n.leaves[0].get_head_name() == "System`Integer" or elem1 < 0 : return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 max_n = min_n + 1 @@ -414,7 +414,7 @@ def apply_2(self, list, n, evaluation): if n_len == 2: elem1 = n_python[0] elem2 = n_python[1] - if elem1 < 0 or elem2 < 0 or not n.leaves[0].get_head_name() == "System`Integer" or not n.leaves[1].get_head_name() == "System`Integer" : + if not n.leaves[0].get_head_name() == "System`Integer" or not n.leaves[1].get_head_name() == "System`Integer" or elem1 < 0 or elem2 < 0 : return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 max_n = elem2 + 1 From ae3dacf7d275d57c2ccdfda189b2887d59424fdc Mon Sep 17 00:00:00 2001 From: larion_dev Date: Tue, 4 Apr 2017 17:18:59 +0700 Subject: [PATCH 3/5] Refactor code for Subsets[] function --- mathics/builtin/combinatorial.py | 55 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index 7c08c77253..502f5f164d 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -353,10 +353,13 @@ class Subsets(Builtin): #> Subsets[{a, b, c}, {}] : Position 2 of Subsets[{a, b, c}, {}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer = Subsets[{a, b, c}, {}] + + #> Subsets[{a, b}, 0] + = {{}} """ rules = { - 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]':'Take[Subsets[list, n], spec]' + 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]':'Take[Subsets[list, n], spec]', } messages = { 'nninfseq': 'Position 2 of `1` must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer', @@ -370,32 +373,27 @@ def apply(self, list, evaluation): def apply_1(self, list, n, evaluation): 'Subsets[list_?ListQ, n_]' - tlist = [x for x in list.leaves] expr = Expression('Subsets', list, n) - if not n.get_head_name() == 'System`Integer' or n.to_python() < 0 : + n_value = n.get_int_value() + if n_value == 0: + return Expression('List', Expression('List')) + if n_value is None or n_value < 0: return evaluation.message('Subsets', 'nninfseq', expr) - tlen = int(n.to_python()) + nested_list = [Expression('List', *c) for i in range(n_value + 1) for c in combinations([x for x in list.leaves], i)] - nested_list = [Expression('List', *c) for i in range(0, tlen + 1) for c in combinations(tlist, i)] + return Expression('List', *nested_list) - result = Expression('List', *nested_list) - - return result - def apply_2(self, list, n, evaluation): 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]]]' - tlist = [x for x in list.leaves] expr = Expression('Subsets', list, n) if n.get_name() == 'System`All' or n.has_form('DirectedInfinity', 1): - tlen = len(tlist) return self.apply(list, evaluation) - n_python = n.to_python() - n_len = len(n_python) + n_len = n.leaves.__len__() if n_len > 3: return evaluation.message('Subsets', 'nninfseq', expr) @@ -404,38 +402,39 @@ def apply_2(self, list, n, evaluation): return evaluation.message('Subsets', 'nninfseq', expr) if n_len == 1: - elem1 = n_python[0] - if not n.leaves[0].get_head_name() == "System`Integer" or elem1 < 0 : + elem1 = n.leaves[0].get_int_value() + if not elem1 or elem1 < 0 : return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 max_n = min_n + 1 step_n = 1 if n_len == 2: - elem1 = n_python[0] - elem2 = n_python[1] - if not n.leaves[0].get_head_name() == "System`Integer" or not n.leaves[1].get_head_name() == "System`Integer" or elem1 < 0 or elem2 < 0 : + elem1 = n.leaves[0].get_int_value() + elem2 = n.leaves[1].get_int_value() + if elem1 is None or elem2 is None or elem1 < 0 or elem2 < 0 : return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 max_n = elem2 + 1 step_n = 1 if n_len == 3: - if not n.leaves[0].get_head_name() == "System`Integer" or not n.leaves[1].get_head_name() == "System`Integer" or not n.leaves[2].get_head_name() == "System`Integer" : + elem1 = n.leaves[0].get_int_value() + elem2 = n.leaves[1].get_int_value() + elem3 = n.leaves[2].get_int_value() + if elem1 is None or elem2 is None or elem3 is None : return evaluation.message('Subsets', 'nninfseq', expr) - step_n = n_python[2] + step_n = elem3 if step_n > 0: - min_n = n_python[0] - max_n = n_python[1] + 1 + min_n = elem1 + max_n = elem2 + 1 elif step_n < 0: - min_n = n_python[0] - max_n = n_python[1] - 1 + min_n = elem1 + max_n = elem2 - 1 else: return evaluation.message('Subsets', 'nninfseq', expr) - nested_list = [Expression('List', *c) for i in range(min_n, max_n, step_n) for c in combinations(tlist, i)] + nested_list = [Expression('List', *c) for i in range(min_n, max_n, step_n) for c in combinations([x for x in list.leaves], i)] - result = Expression('List', *nested_list) + return Expression('List', *nested_list) - return result - From efdefdd4e7fdeb19c0990489690d4acbe501e20b Mon Sep 17 00:00:00 2001 From: larion_dev Date: Mon, 10 Apr 2017 10:47:19 +0700 Subject: [PATCH 4/5] Handling the non list argument in the 1st position for Subsets[] --- mathics/builtin/combinatorial.py | 135 ++++++++++++++++++------------- 1 file changed, 81 insertions(+), 54 deletions(-) diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index 502f5f164d..8bfa60d62c 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -356,6 +356,22 @@ class Subsets(Builtin): #> Subsets[{a, b}, 0] = {{}} + + #> Subsets[{1, 2}, x] + : Position 2 of Subsets[{1, 2}, x] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer + = Subsets[{1, 2}, x] + + #> Subsets[x] + : Nonatomic expression expected at position 1 in Subsets[x]. + = Subsets[x] + + #> Subsets[x, {1, 2}] + : Nonatomic expression expected at position 1 in Subsets[x, {1, 2}]. + = Subsets[x, {1, 2}] + + #> Subsets[x, {1, 2, 3}, {1, 3}] + : Nonatomic expression expected at position 1 in Subsets[x, {1, 2, 3}, {1, 3}]. + = Subsets[x, {1, 2, 3}, {1, 3}] """ rules = { @@ -363,78 +379,89 @@ class Subsets(Builtin): } messages = { 'nninfseq': 'Position 2 of `1` must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer', + 'normal': 'Nonatomic expression expected at position 1 in `1`.' } def apply(self, list, evaluation): - 'Subsets[list_?ListQ]' - - return self.apply_1(list, Integer(len(list.to_python())), evaluation) + 'Subsets[list_]' + expr = Expression('Subsets', list) + return evaluation.message('Subsets', 'normal', expr) if not list.has_form('List', None) else self.apply_1(list, Integer(len(list.to_python())), evaluation) def apply_1(self, list, n, evaluation): - 'Subsets[list_?ListQ, n_]' + 'Subsets[list_, n_]' expr = Expression('Subsets', list, n) - n_value = n.get_int_value() - if n_value == 0: - return Expression('List', Expression('List')) - if n_value is None or n_value < 0: - return evaluation.message('Subsets', 'nninfseq', expr) - - nested_list = [Expression('List', *c) for i in range(n_value + 1) for c in combinations([x for x in list.leaves], i)] - - return Expression('List', *nested_list) + if not list.has_form('List', None): + return evaluation.message('Subsets', 'normal', expr) + else: + n_value = n.get_int_value() + if n_value == 0: + return Expression('List', Expression('List')) + if n_value is None or n_value < 0: + return evaluation.message('Subsets', 'nninfseq', expr) + + nested_list = [Expression('List', *c) for i in range(n_value + 1) for c in combinations(list.leaves, i)] + + return Expression('List', *nested_list) def apply_2(self, list, n, evaluation): - 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]]]' + 'Subsets[list_, Pattern[n,_?ListQ|All|DirectedInfinity[1]]]' expr = Expression('Subsets', list, n) - if n.get_name() == 'System`All' or n.has_form('DirectedInfinity', 1): - return self.apply(list, evaluation) - - n_len = n.leaves.__len__() - - if n_len > 3: - return evaluation.message('Subsets', 'nninfseq', expr) - - if n_len == 0: - return evaluation.message('Subsets', 'nninfseq', expr) - - if n_len == 1: - elem1 = n.leaves[0].get_int_value() - if not elem1 or elem1 < 0 : - return evaluation.message('Subsets', 'nninfseq', expr) - min_n = elem1 - max_n = min_n + 1 - step_n = 1 - - if n_len == 2: - elem1 = n.leaves[0].get_int_value() - elem2 = n.leaves[1].get_int_value() - if elem1 is None or elem2 is None or elem1 < 0 or elem2 < 0 : - return evaluation.message('Subsets', 'nninfseq', expr) - min_n = elem1 - max_n = elem2 + 1 - step_n = 1 + if not list.has_form('List', None): + return evaluation.message('Subsets', 'normal', expr) + else: + if n.get_name() == 'System`All' or n.has_form('DirectedInfinity', 1): + return self.apply(list, evaluation) + + n_len = len(n.leaves) - if n_len == 3: - elem1 = n.leaves[0].get_int_value() - elem2 = n.leaves[1].get_int_value() - elem3 = n.leaves[2].get_int_value() - if elem1 is None or elem2 is None or elem3 is None : + if n_len == 0: return evaluation.message('Subsets', 'nninfseq', expr) - step_n = elem3 - if step_n > 0: + + elif n_len == 1: + elem1 = n.leaves[0].get_int_value() + if elem1 is None or elem1 < 0 : + return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 - max_n = elem2 + 1 - elif step_n < 0: + max_n = min_n + 1 + step_n = 1 + + elif n_len == 2: + elem1 = n.leaves[0].get_int_value() + elem2 = n.leaves[1].get_int_value() + if elem1 is None or elem2 is None or elem1 < 0 or elem2 < 0 : + return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 - max_n = elem2 - 1 + max_n = elem2 + 1 + step_n = 1 + + elif n_len == 3: + elem1 = n.leaves[0].get_int_value() + elem2 = n.leaves[1].get_int_value() + elem3 = n.leaves[2].get_int_value() + if elem1 is None or elem2 is None or elem3 is None : + return evaluation.message('Subsets', 'nninfseq', expr) + step_n = elem3 + if step_n > 0: + min_n = elem1 + max_n = elem2 + 1 + elif step_n < 0: + min_n = elem1 + max_n = elem2 - 1 + else: + return evaluation.message('Subsets', 'nninfseq', expr) else: return evaluation.message('Subsets', 'nninfseq', expr) - nested_list = [Expression('List', *c) for i in range(min_n, max_n, step_n) for c in combinations([x for x in list.leaves], i)] - - return Expression('List', *nested_list) + nested_list = [Expression('List', *c) for i in range(min_n, max_n, step_n) for c in combinations(list.leaves, i)] + + return Expression('List', *nested_list) + def apply_3(self, list, n, spec, evaluation): + 'Subsets[list_, Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]' + expr = Expression('Subsets', list, n, spec) + return evaluation.message('Subsets', 'normal', expr) + \ No newline at end of file From 199494fccd12097fa97378eaf3b16d5f8189faa5 Mon Sep 17 00:00:00 2001 From: larion_dev Date: Tue, 18 Apr 2017 11:09:41 +0700 Subject: [PATCH 5/5] Update Subsets[] for finding subsets from any head in Mathics --- mathics/builtin/combinatorial.py | 88 +++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index 8bfa60d62c..ebffadb4d5 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -293,25 +293,32 @@ class Subsets(Builtin):
finds the $n$th possible subset. + All possible subsets (power set): >> Subsets[{a, b, c}] = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} - - >> Subsets[{a, b, c}, 2] - = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}} - - >> Subsets[{a, b, c}, {2}] - = {{a, b}, {a, c}, {b, c}} - - #> Subsets[{a, b, c, d, e}, {3}, 5] + + All possible subsets containing up to 2 elements: + >> Subsets[{a, b, c, d}, 2] + = {{}, {a}, {b}, {c}, {d}, {a, b}, {a, c}, {a, d}, {b, c}, {b, d}, {c, d}} + + Subsets containing exactly 2 elements: + >> Subsets[{a, b, c, d}, {2}] + = {{a, b}, {a, c}, {a, d}, {b, c}, {b, d}, {c, d}} + + The first 5 subsets containing 3 elements: + >> Subsets[{a, b, c, d, e}, {3}, 5] = {{a, b, c}, {a, b, d}, {a, b, e}, {a, c, d}, {a, c, e}} - #> Subsets[{a, b, c, d, e}, {0, 5, 2}] + All subsets with even length: + >> Subsets[{a, b, c, d, e}, {0, 5, 2}] = {{}, {a, b}, {a, c}, {a, d}, {a, e}, {b, c}, {b, d}, {b, e}, {c, d}, {c, e}, {d, e}, {a, b, c, d}, {a, b, c, e}, {a, b, d, e}, {a, c, d, e}, {b, c, d, e}} - - #> Subsets[Range[5], All, {25}] - = {{2, 4, 5}} - #> Subsets[{a, b, c, d}, All, {15, 1, -2}] + The 25th subset: + >> Subsets[Range[5], All, {25}] + = {{2, 4, 5}} + + The odd-numbered subsets of {a,b,c,d} in reverse order: + >> Subsets[{a, b, c, d}, All, {15, 1, -2}] = {{b, c, d}, {a, b, d}, {c, d}, {b, c}, {a, c}, {d}, {b}, {}} #> Subsets[{}] @@ -372,11 +379,33 @@ class Subsets(Builtin): #> Subsets[x, {1, 2, 3}, {1, 3}] : Nonatomic expression expected at position 1 in Subsets[x, {1, 2, 3}, {1, 3}]. = Subsets[x, {1, 2, 3}, {1, 3}] + + #> Subsets[a + b + c] + = {0, a, b, c, a + b, a + c, b + c, a + b + c} + + #> Subsets[f[a, b, c]] + = {f[], f[a], f[b], f[c], f[a, b], f[a, c], f[b, c], f[a, b, c]} + + #> Subsets[a + b + c, {1, 3, 2}] + = {a, b, c, a + b + c} + + #> Subsets[a* b * c, All, {6}] + = {a c} + + #> Subsets[{a, b, c}, {1, Infinity}] + = {{a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} + + #> Subsets[{a, b, c}, {1, Infinity, 2}] + = {{a}, {b}, {c}, {a, b, c}} + + #> Subsets[{a, b, c}, {3, Infinity, -1}] + = {} """ rules = { - 'Subsets[list_?ListQ , Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]':'Take[Subsets[list, n], spec]', + 'Subsets[list_ , Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]':'Take[Subsets[list, n], spec]', } + messages = { 'nninfseq': 'Position 2 of `1` must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer', 'normal': 'Nonatomic expression expected at position 1 in `1`.' @@ -384,24 +413,24 @@ class Subsets(Builtin): def apply(self, list, evaluation): 'Subsets[list_]' - expr = Expression('Subsets', list) - return evaluation.message('Subsets', 'normal', expr) if not list.has_form('List', None) else self.apply_1(list, Integer(len(list.to_python())), evaluation) - + + return evaluation.message('Subsets', 'normal', Expression('Subsets', list)) if list.is_atom() else self.apply_1(list, Integer(len(list.leaves)), evaluation) + def apply_1(self, list, n, evaluation): 'Subsets[list_, n_]' expr = Expression('Subsets', list, n) - - if not list.has_form('List', None): + if list.is_atom(): return evaluation.message('Subsets', 'normal', expr) else: + head_t = list.head n_value = n.get_int_value() if n_value == 0: return Expression('List', Expression('List')) if n_value is None or n_value < 0: return evaluation.message('Subsets', 'nninfseq', expr) - nested_list = [Expression('List', *c) for i in range(n_value + 1) for c in combinations(list.leaves, i)] + nested_list = [Expression(head_t, *c) for i in range(n_value + 1) for c in combinations(list.leaves, i)] return Expression('List', *nested_list) @@ -409,10 +438,11 @@ def apply_2(self, list, n, evaluation): 'Subsets[list_, Pattern[n,_?ListQ|All|DirectedInfinity[1]]]' expr = Expression('Subsets', list, n) - - if not list.has_form('List', None): + + if list.is_atom(): return evaluation.message('Subsets', 'normal', expr) else: + head_t = list.head if n.get_name() == 'System`All' or n.has_form('DirectedInfinity', 1): return self.apply(list, evaluation) @@ -431,7 +461,7 @@ def apply_2(self, list, n, evaluation): elif n_len == 2: elem1 = n.leaves[0].get_int_value() - elem2 = n.leaves[1].get_int_value() + elem2 = n.leaves[1].get_int_value() if not n.leaves[1].has_form('DirectedInfinity', 1) else len(list.leaves) + 1 if elem1 is None or elem2 is None or elem1 < 0 or elem2 < 0 : return evaluation.message('Subsets', 'nninfseq', expr) min_n = elem1 @@ -440,9 +470,9 @@ def apply_2(self, list, n, evaluation): elif n_len == 3: elem1 = n.leaves[0].get_int_value() - elem2 = n.leaves[1].get_int_value() + elem2 = n.leaves[1].get_int_value() if not n.leaves[1].has_form('DirectedInfinity', 1) else len(list.leaves) + 1 elem3 = n.leaves[2].get_int_value() - if elem1 is None or elem2 is None or elem3 is None : + if elem1 is None or elem2 is None or elem3 is None or elem1 < 0 or elem2 < 0: return evaluation.message('Subsets', 'nninfseq', expr) step_n = elem3 if step_n > 0: @@ -456,12 +486,12 @@ def apply_2(self, list, n, evaluation): else: return evaluation.message('Subsets', 'nninfseq', expr) - nested_list = [Expression('List', *c) for i in range(min_n, max_n, step_n) for c in combinations(list.leaves, i)] + nested_list = [Expression(head_t, *c) for i in range(min_n, max_n, step_n) for c in combinations(list.leaves, i)] return Expression('List', *nested_list) def apply_3(self, list, n, spec, evaluation): - 'Subsets[list_, Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]' - expr = Expression('Subsets', list, n, spec) - return evaluation.message('Subsets', 'normal', expr) + 'Subsets[list_?AtomQ, Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]' + + return evaluation.message('Subsets', 'normal', Expression('Subsets', list, n, spec)) \ No newline at end of file