Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace[] #478

Merged
merged 3 commits into from
Aug 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -161,6 +244,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 @@ -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, 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 @@ -1076,27 +1083,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