From bc14ec3a93589209e4aa9578e62e242cd1e2e2a0 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Wed, 10 Aug 2016 20:50:37 +0200 Subject: [PATCH 1/3] a basic Replace[] implementation --- mathics/builtin/lists.py | 2 ++ mathics/builtin/patterns.py | 60 +++++++++++++++++++++++++++++++++++++ mathics/core/expression.py | 40 ++++++++++++++++++++----- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index ebceb81c4a..cd3f5b4718 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -483,6 +483,8 @@ def value_to_level(expr): return values[0], values[1] else: raise InvalidLevelspecError + elif isinstance(levelspec, Symbol) and levelspec.get_name() == 'System`All': + return 0, None else: return 1, value_to_level(levelspec) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 1fe89a90c4..b406c65d25 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -40,6 +40,7 @@ from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator from mathics.builtin.base import PatternObject +from mathics.builtin.lists import python_levelspec from mathics.core.expression import ( Symbol, Expression, Number, Integer, Rational, Real) @@ -128,6 +129,65 @@ def create_rules(rules_expr, expr, name, evaluation, extra_args=[]): return result, False +class Replace(Builtin): + """ +
+
'Replace[$expr$, $x$ -> $y$]' +
yields the result of replacing $expr$ with $y$ if it + matches the pattern $x$. +
'Replace[$expr$, $x$ -> $y$, $levelspec$]' +
replaces only subexpressions at levels specified through + $levelspec$. +
'Replace[$expr$, {$x$ -> $y$, ...}]' +
performs replacement with multiple rules, yielding a + single result expression. +
'Replace[$expr$, {{$a$ -> $b$, ...}, {$c$ -> $d$, ...}, ...}]' +
returns a list containing the result of performing each + set of replacements. +
+ + >> Replace[x, {x -> 2}] + = 2 + + By default, only the top level gets replaced. + >> Replace[1 + x, {x -> 2}] + = 1 + x + + >> Replace[x, {{x -> 1}, {x -> 2}}] + = {1, 2} + + You can use Replace as an operator: + >> Replace[{x_ -> x + 1}][10] + = 11 + """ + + messages = { + 'reps': "`1` is not a valid replacement rule.", + 'rmix': "Elements of `1` are a mixture of lists and nonlists.", + } + + rules = { + 'Replace[rules_][expr_]': 'Replace[expr, rules]', + } + + def _replace(self, expr, rules, levelspec, evaluation): + rules, ret = create_rules(rules, expr, 'Replace', evaluation) + if ret: + return rules + + result, applied = expr.apply_rules( + rules, evaluation, level=0, levelspec=levelspec) + return result + + def apply(self, expr, rules, evaluation): + 'Replace[expr_, rules_]' + return self._replace(expr, rules, (0, 0), evaluation) + + def apply_levelspec(self, expr, rules, levelspec, evaluation): + 'Replace[expr_, rules_, levelspec_]' + return self._replace(expr, rules, python_levelspec(levelspec), evaluation) + + class ReplaceAll(BinaryOperator): """
diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 81f45aa01e..45d55b06bf 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -227,7 +227,14 @@ def get_sequence(self): def evaluate_leaves(self, evaluation): return self - def apply_rules(self, rules, evaluation): + def apply_rules(self, rules, evaluation, level=0, levelspec=None): + if levelspec is not None: + l1, l2 = levelspec + if level < l1: + return self, False + elif l2 is not None and level > l2: + return self, False + for rule in rules: result = rule.apply(self, evaluation, fully=False) if result is not None: @@ -1076,22 +1083,39 @@ def filter_leaves(self, head_name): return [leaf for leaf in self.leaves if leaf.get_head_name() == head_name] - def apply_rules(self, rules, evaluation): + def apply_rules(self, rules, evaluation, level=0, levelspec=None): """for rule in rules: result = rule.apply(self, evaluation, fully=False) if result is not None: return result""" - result, applied = super( - Expression, self).apply_rules(rules, evaluation) - if applied: - return result, True - head, applied = self.head.apply_rules(rules, evaluation) + + if levelspec is None: + apply = True + else: + l1, l2 = levelspec + if level < l1: + apply = False + elif l2 is not None and level > l2: + return self, False + else: + apply = True + + if apply: + result, applied = super( + Expression, self).apply_rules(rules, evaluation, level, levelspec) + if applied: + return result, True + head, applied = self.head.apply_rules(rules, evaluation, level, levelspec) + else: + head = self.head + applied = False # to be able to access it inside inner function new_applied = [applied] def apply_leaf(leaf): - new, sub_applied = leaf.apply_rules(rules, evaluation) + new, sub_applied = leaf.apply_rules( + rules, evaluation, level + 1, levelspec) new_applied[0] = new_applied[0] or sub_applied return new From 2f1337b39bfb33bd4644473196d28e0a86665c8b Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Thu, 11 Aug 2016 05:10:23 +0200 Subject: [PATCH 2/3] details of depth first replacement for Replace[] --- mathics/builtin/patterns.py | 18 ++++++++-- mathics/core/expression.py | 66 +++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index b406c65d25..ad131a13c0 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -149,14 +149,22 @@ class Replace(Builtin): >> Replace[x, {x -> 2}] = 2 - By default, only the top level gets replaced. + By default, only the top level is searched for matches >> Replace[1 + x, {x -> 2}] = 1 + x >> Replace[x, {{x -> 1}, {x -> 2}}] = {1, 2} - You can use Replace as an operator: + Replace stops after the first replacement + >> Replace[x, {x -> {}, _List -> y}] + = {} + + Replace replaces the deepest levels first + >> Replace[x[1], {x[1] -> y, 1 -> 2}, All] + = x[2] + + You can use Replace as an operator >> Replace[{x_ -> x + 1}][10] = 11 """ @@ -176,7 +184,7 @@ def _replace(self, expr, rules, levelspec, evaluation): return rules result, applied = expr.apply_rules( - rules, evaluation, level=0, levelspec=levelspec) + rules, evaluation, level=0, options={'levelspec': levelspec}) return result def apply(self, expr, rules, evaluation): @@ -221,6 +229,10 @@ class ReplaceAll(BinaryOperator): #> a + b /. x_ + y_ -> {x, y} = {a, b} + + ReplaceAll replaces the shallowest levels first: + >> ReplaceAll[x[1], {x[1] -> y, 1 -> 2}] + = y """ operator = '/.' diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 45d55b06bf..abfd3af737 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -227,9 +227,9 @@ def get_sequence(self): def evaluate_leaves(self, evaluation): return self - def apply_rules(self, rules, evaluation, level=0, levelspec=None): - if levelspec is not None: - l1, l2 = levelspec + def apply_rules(self, rules, evaluation, level=0, options=None): + if options: + l1, l2 = options['levelspec'] if level < l1: return self, False elif l2 is not None and level > l2: @@ -1083,44 +1083,52 @@ def filter_leaves(self, head_name): return [leaf for leaf in self.leaves if leaf.get_head_name() == head_name] - def apply_rules(self, rules, evaluation, level=0, levelspec=None): + def apply_rules(self, rules, evaluation, level=0, options=None): """for rule in rules: result = rule.apply(self, evaluation, fully=False) if result is not None: return result""" - if levelspec is None: - apply = True - else: - l1, l2 = levelspec - if level < l1: - apply = False - elif l2 is not None and level > l2: - return self, False - else: - apply = True - - if apply: - result, applied = super( - Expression, self).apply_rules(rules, evaluation, level, levelspec) - if applied: - return result, True - head, applied = self.head.apply_rules(rules, evaluation, level, levelspec) - else: - head = self.head - applied = False - # to be able to access it inside inner function - new_applied = [applied] + new_applied = [False] def apply_leaf(leaf): new, sub_applied = leaf.apply_rules( - rules, evaluation, level + 1, levelspec) + rules, evaluation, level + 1, options) new_applied[0] = new_applied[0] or sub_applied return new - return (Expression(head, *[apply_leaf(leaf) for leaf in self.leaves]), - new_applied[0]) + def descend(expr): + return Expression(expr.head, *[apply_leaf(leaf) for leaf in expr.leaves]) + + if options is None: # default ReplaceAll mode; replace breadth first + result, applied = super( + Expression, self).apply_rules(rules, evaluation, level, options) + if applied: + return result, True + head, applied = self.head.apply_rules(rules, evaluation, level, options) + new_applied[0] = applied + return descend(Expression(head, *self.leaves)), new_applied[0] + else: # Replace mode; replace depth first + l1, l2 = options['levelspec'] + if level < l1: + apply_this_level = False + elif l2 is not None and level > l2: + return self, False + else: + apply_this_level = True + + expr = descend(self) + if apply_this_level: + expr, applied = super( + Expression, expr).apply_rules(rules, evaluation, level, options) + new_applied[0] = new_applied[0] or applied + if not applied: + head, applied = expr.head.apply_rules(rules, evaluation, level, options) + new_applied[0] = new_applied[0] or applied + expr = Expression(head, *expr.leaves) + return expr, new_applied[0] + def replace_vars(self, vars, options=None, in_scoping=True, in_function=True): From 56445d3527a4ee9b81cead18b3a4091b43bbaa42 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Thu, 11 Aug 2016 05:34:09 +0200 Subject: [PATCH 3/3] heads options and levels --- mathics/builtin/patterns.py | 43 +++++++++++++++++++++++++------------ mathics/core/expression.py | 22 ++++++------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index ad131a13c0..1675f9d134 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -40,7 +40,7 @@ from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator from mathics.builtin.base import PatternObject -from mathics.builtin.lists import python_levelspec +from mathics.builtin.lists import python_levelspec, InvalidLevelspecError from mathics.core.expression import ( Symbol, Expression, Number, Integer, Rational, Real) @@ -164,6 +164,18 @@ class Replace(Builtin): >> Replace[x[1], {x[1] -> y, 1 -> 2}, All] = x[2] + By default, heads are not replaced + >> Replace[x[x[y]], x -> z, All] + = x[x[y]] + + Heads can be replaced using the Heads option + >> Replace[x[x[y]], x -> z, All, Heads -> True] + = z[z[y]] + + Note that heads are handled at the level of leaves + >> Replace[x[x[y]], x -> z, {1}, Heads -> True] + = z[x[y]] + You can use Replace as an operator >> Replace[{x_ -> x + 1}][10] = 11 @@ -178,22 +190,25 @@ class Replace(Builtin): 'Replace[rules_][expr_]': 'Replace[expr, rules]', } - def _replace(self, expr, rules, levelspec, evaluation): - rules, ret = create_rules(rules, expr, 'Replace', evaluation) - if ret: - return rules + options = { + 'Heads': 'False', + } - result, applied = expr.apply_rules( - rules, evaluation, level=0, options={'levelspec': levelspec}) - return result + def apply_levelspec(self, expr, rules, ls, evaluation, options): + 'Replace[expr_, rules_, Optional[Pattern[ls, _?LevelQ], {0}], OptionsPattern[Replace]]' + try: + rules, ret = create_rules(rules, expr, 'Replace', evaluation) + if ret: + return rules - def apply(self, expr, rules, evaluation): - 'Replace[expr_, rules_]' - return self._replace(expr, rules, (0, 0), evaluation) + heads = self.get_option(options, 'Heads', evaluation).is_true() - def apply_levelspec(self, expr, rules, levelspec, evaluation): - 'Replace[expr_, rules_, levelspec_]' - return self._replace(expr, rules, python_levelspec(levelspec), evaluation) + result, applied = expr.apply_rules( + rules, evaluation, level=0, options={ + 'levelspec': python_levelspec(ls), 'heads': heads}) + return result + except InvalidLevelspecError: + evaluation.message('General', 'level', ls) class ReplaceAll(BinaryOperator): diff --git a/mathics/core/expression.py b/mathics/core/expression.py index abfd3af737..59b4879dd3 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -1110,23 +1110,15 @@ def descend(expr): new_applied[0] = applied return descend(Expression(head, *self.leaves)), new_applied[0] else: # Replace mode; replace depth first - l1, l2 = options['levelspec'] - if level < l1: - apply_this_level = False - elif l2 is not None and level > l2: - return self, False - else: - apply_this_level = True - expr = descend(self) - if apply_this_level: - expr, applied = super( - Expression, expr).apply_rules(rules, evaluation, level, options) + expr, applied = super( + Expression, expr).apply_rules(rules, evaluation, level, options) + new_applied[0] = new_applied[0] or applied + if not applied and options['heads']: + # heads in Replace are treated at the level of the arguments, i.e. level + 1 + head, applied = expr.head.apply_rules(rules, evaluation, level + 1, options) new_applied[0] = new_applied[0] or applied - if not applied: - head, applied = expr.head.apply_rules(rules, evaluation, level, options) - new_applied[0] = new_applied[0] or applied - expr = Expression(head, *expr.leaves) + expr = Expression(head, *expr.leaves) return expr, new_applied[0]