How to use each replacement only once in a list of replacements
One way is to use the following:
GeneralUtilities`ListIterator
GeneralUtilities`IteratorExhausted
Then it can be done with Replace
or ReplaceAll
:
Needs@"GeneralUtilities`"
Module[{hold},
SetAttributes[hold, HoldAll];
oneTimeRules[rules_] :=
Normal@Merge[rules, ListIterator] /. Rule -> RuleDelayed /.
i_GeneralUtilities`Iterator :>
With[{r = Read[i]}, hold[r, r =!= IteratorExhausted]] /.
hold -> Condition;
];
Example:
Replace[{a, b, a, a, b, b}, oneTimeRules@{a -> 1, a -> 2, b -> 3}, 1]
(* {1, 3, 2, a, b, b} *)
It does not work with patterns:
Replace[{a, b, a, a, b, b},
oneTimeRules@{x_ -> f[x], x_ -> 2, b -> 3}, 1]
(* {f[x], 2, a, a, 3, b} <-- should be f[a] *)
Addendum
I thought this modification of @Nasser's approach (now deleted), derived from , seemed a better idea. It seems to work with patterns.
ClearAll[useOnce, useRepeated];
SetAttributes[useRepeated, Listable];
useRepeated[(Rule | RuleDelayed)[pat_, repl_], n_ : 1] :=
Module[{used = 0},
pat :> repl /; used++ < n
];
useOnce[r_] := useRepeated[r];
Replace[{a, b, a, a, b, b}, useOnce@{a -> 1, a -> 2, b -> 3}, 1]
(* {1, 3, 2, a, b, b} *)
Replace[{a, b, a, a, b, b}, useOnce@{x_ -> f[x], x_ -> 2 x, b -> 3}, 1]
(* {f[a], 2 b, a, a, 3, b} *)
The function useRepeated
lets a rule be applied up to n
times, by default 1
. The function useOnce
is shorthand useRepeated
with n = 1
. The *Iterator
family uses similar internal data to keep track of where an Iterator
is, so if I were using this, I'd prefer useOnce
.
This uses FirstPosition
and ReplacePart
. It works on the OP's example, not sure how extensible it is.
replaceOnceOnly[expr_, rules_] := Module[{val = expr, part},
Function[
If[Not[MissingQ[part = FirstPosition[val, #]]],
val = ReplacePart[val, part -> #2]
]
]@@@rules;
val
];
In[14]:= replaceOnceOnly[{a, b, a}, {a -> 1, a -> 2, b -> 3}]
Out[14]= {1, 3, 2}
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 *)