Here a solution which will adjust the behaviour of a set of rules which contains "duplicate" rules:
adjust[strategy_, rules_] :=
Hold@@@GatherBy[rules, First] //
Map[With[{vs = #[[All, 2]]}, strategy[RuleDelayed@@{#[[1, 1]], Unevaluated@vs}]]&]
cycle[k_ :> vs_] := Module[{i = 0}, k :> vs[[1+Mod[i++, Length@vs]]]]
oneshot[k_ :> vs_] := Module[{i = 0}, k :> Module[{ii = ++i}, vs[[ii]] /; ii <= Length[vs]]]
padlast[k_ :> vs_] := Module[{i = 0}, k :> vs[[Min[++i, Length[vs]]]]]
normal[k_ :> _[v_, ___]] := k :> v
Multiple Strategies
The various strategies are...
... cycle
which cycles through the rules repeatedly:
{a, b, a, b, b, b, a, a} /. adjust[cycle, {a -> 1, a -> 2, b -> 3}]
(* {1, 3, 2, 3, 3, 3, 1, 2} *)
... padlast
which reuses the last rule as "padding":
{a, b, a, b, b, b, a, a} /. adjust[padlast, {a -> 1, a -> 2, b -> 3}]
(* {1, 3, 2, 3, 3, 3, 2, 2} *)
... oneshot
which just lets the rules run out:
{a, b, a, b, b, b, a, a} /. adjust[oneshot, {a -> 1, a -> 2, b -> 3}]
(* {1, 3, 2, b, b, b, a, a} *)
... and normal
which is the regular behaviour where extra "duplicates" are ignored:
{a, b, a, b, b, b, a, a} /. adjust[normal, {a -> 1, a -> 2, b -> 3}]
(* {1, 3, 1, 3, 3, 3, 1, 1} *)
RuleDelayed Replacements
The solution also supports replacements that use RuleDelayed
(:>
).
Given:
$rules =
{ f[x_] :> x, f[x_] :> 10x, f[x_] :> 100x
, g[x_] :> -x, g[x_] :> -10x
, h[x_] :> Echo["Evaluation Leak!"]
};
$exprs = {f[1], g[2], f[3], g[4], f[5], f[6], g[7]};
Then:
$exprs /. adjust[cycle, $rules]
(* {1,-2,30,-40,500,6,-7} *)
$exprs /. adjust[oneshot, $rules]
(* {1,-2,30,-40,500,f[6],g[7]} *)
$exprs /. adjust[padlast, $rules]
(* {1,-2,30,-40,500,600,-70} *)
$exprs /. adjust[normal, $rules]
(* {1,-2,3,-4,5,6,-7} *)
Limited Support for Condition
Beware that the exhibited implementation only supports Condition
(/;
) on the left-hand side of a rule:
Range[10] /.
adjust[cycle, {x_ /; x < 7 :> "small", x_ /; x < 7 :> "little"}]
(* {small,little,small,little,small,little,7,8,9,10} *)
It does not support conditions on the right-hand side, whether "bare" or nested within a scoping construct:
1 /. adjust[cycle, {x_ :> "small" /; x < 7}]
(* incorrect result: small /; 1 < 7 *)
b^a + 2 a
versusb^a + x a
, in which the orderless property sorts the power term into a different position. Also{a b, b, a}
. $\endgroup$