Add/replace a rule in a list of rules based upon the rule's lhs without regard for the rule's rhs

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}

• 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. Commented May 23, 2012 at 20:44

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}

• Thanks for pointing FilterRules that is what I have been looking. Would have been nice if FilterRules was in Rule's SEE ALSO section. Commented May 23, 2012 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}}.
– kglr
Commented May 23, 2012 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
Commented May 24, 2012 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.
– kglr
Commented May 24, 2012 at 8:41
• @kguler ...weird....weird
– Rojo
Commented May 24, 2012 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.

• He doesn't want to replace only, also add if it isn't there
– Rojo
Commented May 23, 2012 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
Commented May 24, 2012 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
Commented May 24, 2012 at 1:44
• @RM, sorry, I hadn't seen you had considered the fact it sometimes simply prepends
– Rojo
Commented May 24, 2012 at 4:27
– rm -rf
Commented May 24, 2012 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}


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}}