Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I have the following lists of rules:

{x -> 1, y -> 2}
{x -> 1, y -> 2, z -> 4}
{x -> 1, y -> 2, z -> 2}

For each list of rules, I would like to add the rule z -> 3 or replace any rule whose lhs is z
with z -> 3 .

I am able to get this to work for specific rules, for example z -> 4

AddReplaceRule[rules_List] :=
 Module[{newRules},
  If [MemberQ[rules, z -> 4],
   newRules = DeleteCases[rules, z -> 4];
   ,
   newRules = rules;
   ];
  Append[newRules, z -> 3]
  ]

Results

Note I am expecting to see {x -> 1, y -> 2, z -> 3} for all three cases, but that is not the case, since I need to generalize z -> 4 in AddReplaceRule.

In[]:= AddReplaceRule[{x -> 1, y -> 2}]
Out[]= {x -> 1, y -> 2, z -> 3}

In[]:= AddReplaceRule[{x -> 1, y -> 2, z -> 4}]
Out[]= {x -> 1, y -> 2, z -> 3}

In[]:= AddReplaceRule[{x -> 1, y -> 2, z -> 2}]
Out[]= {x -> 1, y -> 2, z -> 2, z -> 3}
share|improve this question
5  
Note that, if you intend to apply these rules, you can simply Prepend your new rule to the list. You will then have rules with the same l.h.s., true, but the new rule will always apply first, just because of the way rule application works. Unless these operations are so frequent that you grow truly huge list of rules, this should work fine. And if you do, you can periodically use Dispatch on these rules. You can even set up a wrapper which would do that automatically, which, however, is slightly more complex and may be destroying the purpose. –  Leonid Shifrin May 23 '12 at 20:44

4 Answers 4

up vote 8 down vote accepted
r1 = {x -> 1, y -> 2};
r2 = {x -> 1, y -> 2, z -> 4};
r3 = {x -> 1, y -> 2, z -> 2};

An idea could be

changeRule[rule : (sym_ -> _)] := Append[
   FilterRules[#, Except[sym]], rule] &

So

changeRule[z -> 3] /@ {r1, r2, r3}

{{x -> 1, y -> 2, z -> 3}, {x -> 1, y -> 2, z -> 3}, {x -> 1, y -> 2, z -> 3}}

EDIT

Other versions avoiding FilterRules

changeRulesV2[rule_] := 
 DeleteDuplicates[Prepend[#, rule], First[#1] === First[#2] &] &
changeRulesV3[rule : (sym_ -> _)] := 
 Append[DeleteCases[#, sym -> _], rule] &

Here's a version that works for several rules at once, inspider by @kguler's comment

changeRuleV4[rules : (_ -> _) ..] := 
 Join[FilterRules[#, Except@Alternatives[rules][[All, 1]]], {rules}] &

changeRuleV4[z -> 9, h -> 99][{x -> 1, y -> 2, z -> 4}]

{x -> 1, y -> 2, z -> 9, h -> 99}

share|improve this answer
    
Thanks for pointing FilterRules that is what I have been looking. Would have been nice if FilterRules was in Rule's SEE ALSO section. –  mmorris May 23 '12 at 20:43
    
Nice (+1)... Just a thought: with a slight modification, the same idea can be used for adding/modifying multiple rules: for example, replaceOrAddRules = Function[{rules, newrules}, FilterRules[rules, Except[Rule[First@#, _] & /@ newrules]]~Join~ newrules]. Applied as replaceOrAddRules[#, {z -> 3, x -> 5, w -> 1}] & /@ {r1, r2, r3} gives {{y -> 2, z -> 3, x -> 5, w -> 1}, {y -> 2, z -> 3, x -> 5, w -> 1}, {y -> 2, z -> 3, x -> 5, w -> 1}}. –  kguler May 23 '12 at 23:19
    
@kguler, I don't understand why your pattern Except[{x->_, y->_}] works in FilterRules, but it does. However, Except[(x->_)|(y->_)] doesn't, Except[x|y] does (my version, this I get), and Except[{x, y}] doesn't. Any ideas? –  Rojo May 24 '12 at 4:46
    
Rojo, I tried to make Except[...] to produce something that conforms to the second argument of FilterRules, a pattern or a list of patterns. When the second argument of FilterRules is a list, all of the following {x,y}, {x->_,y->_},{x:>_,y},{x->_,y:>_},{x,y->_} work. If I use any of these inside Except I get the expected results. When the second argument is not a list, x|y and Except[x|y] works. But using (x->_)|(y->_) yields {}, and the same pattern inside Except gives the first argument back. It is indeed puzzling Except[{x,y}] did not work in your case. –  kguler May 24 '12 at 8:41
    
@kguler ...weird....weird –  Rojo May 24 '12 at 9:11

You can use rules to replace rules! Here's an example:

{x -> 1, y -> 2, z -> 2} /. HoldPattern[z -> _] :> (z -> 3)
(* {x -> 1, y -> 2, z -> 3} *)

Now when there isn't a rule involving z and you want to add the new one to the existing rules, it becomes a bit more involved and inelegant — you should go with Rojo's solution. Nevertheless, I'll include the following if only to highlight Mathematica's flexibility.

For the general case when there may or may not be a rule involving z, the following works (thanks to Rojo for improving):

Replace[oldRules, {x___, Longest[PatternSequence[] | (z -> _)], y___} :> {x, newRule, y}]

For example:

Replace[{x -> 1, y -> 2}, {x___, Longest[PatternSequence[] | (z -> _)], y___} :> {x, z -> 3, y}]
(* {z -> 3, x -> 1, y -> 2} *)

This will be slower (at least, for longer lists) due to the use of BlankNullSequence.

share|improve this answer
1  
He doesn't want to replace only, also add if it isn't there –  Rojo May 23 '12 at 21:46
    
Now I like it, +1. But it fails, for example, for {z -> 4, y -> 2, x -> 1}. You can fix it with Longest, as in this alternative that searches only the first level Replace[{x -> 1, y -> 2}, {x___, Longest[PatternSequence[] | (z -> _)], y___} :> {x, z -> 3, y}] –  Rojo May 24 '12 at 1:43
    
@Rojo How does it fail? I still get z -> 3 as the first element. The pattern should give empty for x___, a match for HoldPattern[...] and the remaining for y___. The second z -> _ rule doesn't matter because it won't be applied. But I like your suggestion using Longest –  rm -rf May 24 '12 at 1:44
    
@RM, sorry, I hadn't seen you had considered the fact it sometimes simply prepends –  Rojo May 24 '12 at 4:27
    
@Rojo I added your suggestion, which was better than what I had. –  rm -rf May 24 '12 at 4:32

Had FilterRules[] not been available, I would have done something like this:

addReplaceRule[ruleList : {(_Rule | _RuleDelayed) ..}, newRule : _Rule | _RuleDelayed] := 
 Module[{patt = newRule /. ((a_ -> b_) | (a_ :> b_)) :> ((a -> _) | (a :> _))}, 
  If[FreeQ[ruleList, patt], Append[ruleList, newRule], 
   ruleList /. patt -> newRule]]

Try it out:

addReplaceRule[{x -> 1, y -> 2, z -> 5}, z -> 3]
{x -> 1, y -> 2, z -> 3}

addReplaceRule[{x -> 1, y -> 2}, z -> 3]
{x -> 1, y -> 2, z -> 3}

addReplaceRule[{x -> 1, y -> 2, z -> 3}, z :> 3]
{x -> 1, y -> 2, z :> 3}

addReplaceRule[{x -> 1, y -> 2, z :> 3}, z -> 3]
{x -> 1, y -> 2, z -> 3}
share|improve this answer

One more way.

Position checks where, if at all, there is an existing rule that has the targeted lhs. If such a rule is not in rules, Position will return {} and the newRule is appended. Otherwise, the old rule is replaced by the newRule.

f[rules_, newRule_] :=
   Module[{pos = Position[rules, (newRule[[1]] -> _)]},
   If[pos == {}, Append[rules, newRule], ReplacePart[rules, pos[[1]] -> newRule]]]

So

f[#, (z -> 3)] & /@ {{x -> 1, y -> 2}, {x -> 1, y -> 2, z -> 4}, {x -> 1, y -> 2, z -> 2}}

(* out *)
{{x -> 1, y -> 2, z -> 3}, {x -> 1, y -> 2, z -> 3}, {x -> 1, y -> 2,  z -> 3}}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.