Skip to content

Commit

Permalink
Merge pull request #478 from poke1024/replace
Browse files Browse the repository at this point in the history
Replace[]
  • Loading branch information
sn6uv authored Aug 18, 2016
2 parents 7d3fdfa + 56445d3 commit 7747363
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 11 deletions.
2 changes: 2 additions & 0 deletions mathics/builtin/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
87 changes: 87 additions & 0 deletions mathics/builtin/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, InvalidLevelspecError

from mathics.core.expression import (
Symbol, Expression, Number, Integer, Rational, Real)
Expand Down Expand Up @@ -128,6 +129,88 @@ def create_rules(rules_expr, expr, name, evaluation, extra_args=[]):
return result, False


class Replace(Builtin):
"""
<dl>
<dt>'Replace[$expr$, $x$ -> $y$]'
<dd>yields the result of replacing $expr$ with $y$ if it
matches the pattern $x$.
<dt>'Replace[$expr$, $x$ -> $y$, $levelspec$]'
<dd>replaces only subexpressions at levels specified through
$levelspec$.
<dt>'Replace[$expr$, {$x$ -> $y$, ...}]'
<dd>performs replacement with multiple rules, yielding a
single result expression.
<dt>'Replace[$expr$, {{$a$ -> $b$, ...}, {$c$ -> $d$, ...}, ...}]'
<dd>returns a list containing the result of performing each
set of replacements.
</dl>
>> Replace[x, {x -> 2}]
= 2
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}
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]
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
"""

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]',
}

options = {
'Heads': 'False',
}

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

heads = self.get_option(options, 'Heads', evaluation).is_true()

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):
"""
<dl>
Expand Down Expand Up @@ -165,6 +248,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 = '/.'
Expand Down
46 changes: 35 additions & 11 deletions mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,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, options=None):
if options:
l1, l2 = options['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:
Expand Down Expand Up @@ -1078,27 +1085,44 @@ 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, options=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)

# 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)
new, sub_applied = leaf.apply_rules(
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
expr = descend(self)
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
expr = Expression(head, *expr.leaves)
return expr, new_applied[0]


def replace_vars(self, vars, options=None,
in_scoping=True, in_function=True):
Expand Down

0 comments on commit 7747363

Please sign in to comment.