6
$\begingroup$

Given the following:

Clear[a, b, c, d, e, f, g, h];

{a, b, c, d, e, f, g, h} /. {a -> 12, c -> 12, e -> 12, f -> 12}

(* {12, b, 12, d, 12, 12, g, h} *)  

Is there a more elegant or shorter notation to replace multiple symbols with a similar value?

$\endgroup$
  • $\begingroup$ These kind of problems are best solved using the Map function. $\endgroup$ – Mockup Dungeon Jan 26 '15 at 9:37
12
$\begingroup$

You can have a single rule using Alternatives (|)...

{a, b, c, d, e, f, g, h} /. x : (a | c | e | f) -> 12

Furthermore you can construct the rule on the fly...

{a, b, c, d, e, f, g, h} /. x : Alternatives@@{a, c, e, f} -> 12

As noted by @Kuba below there is no requirement for the pattern to have a name (x) so...

{a, b, c, d, e, f, g, h} /. (a | c | e | f) -> 12
{a, b, c, d, e, f, g, h} /. Alternatives @@ {a, c, e, f} -> 12

also work.

$\endgroup$
  • 2
    $\begingroup$ Now that was kind of alternative (no pun intended) I was hoping for. Thanks Ymareth. $\endgroup$ – MathLind Jan 26 '15 at 9:44
  • 1
    $\begingroup$ There is no need to name those patterns with x:. $\endgroup$ – Kuba Jan 26 '15 at 9:47
  • $\begingroup$ The second alternative maybe even more useful since you can use a part of the original list. $\endgroup$ – MathLind Jan 26 '15 at 9:47
  • $\begingroup$ @MathLind But keep the pattern name if you want to use the symbols as arguments, ie. x : (a | c | e | f) :> 12 x. $\endgroup$ – BoLe Jan 26 '15 at 20:11
5
$\begingroup$

I like using Thread for this kind of rules

{a, b, c, d, e, f, g, h} /. Thread[{a, c, e, f} -> Table[12, {4}]]

As @kguler suggests there is no need having a Table and keeping track of the length of the list also for a single value.

So the following is better!

{a, b, c, d, e, f, g, h} /. Thread[{a, c, e, f} -> 12]

$\endgroup$
  • 3
    $\begingroup$ Simpler Thread[{a, c, e, f} -> 12] also works. $\endgroup$ – kglr Jan 26 '15 at 11:46
  • $\begingroup$ You are right! I see it's because of head h being a List by default on Thread[{a,c,e,f}->11,h] $\endgroup$ – tchronis Jan 26 '15 at 12:10
  • 1
    $\begingroup$ @MathLind, i think it might be better if tchronis could update -- if he agrees to - his answer adding the alternative syntax for Thread. $\endgroup$ – kglr Jan 26 '15 at 16:32
  • 1
    $\begingroup$ Added @kugler 's syntax. $\endgroup$ – tchronis Jan 26 '15 at 16:37
4
$\begingroup$

You asked for "more elegant or shorter" and Ymareth gave you exactly that. However it is worth noting that with long Alternatives this comes at a performance cost. Please observe:

Needs["GeneralUtilities`"]

set = Range[20000];

fn1[x_] := set /. (Alternatives @@ x -> -1)
fn2[x_] := set /. (Dispatch @ Thread[x -> -1])

BenchmarkPlot[{fn1, fn2}, RandomChoice[set, #] &, 2^Range[14]]

enter image description here

If you do not include the time taken by Dispatch to optimize the table the second method has an even larger advantage, which is applicable if the rules will be used more than once.

As a guideline if it is short enough to type using a | b | c | . . . you're fine, but if using Alternatives @@ at least consider Dispatch.


Ymareth wrote:

It would be interesting to know, if at extreme values of n, the gradients are the same; dispatch just has a much lower constant multiplier or dispatch's cost is growing slower regardless of n.

The complexity of Thread and Dispatch seems pretty consistent:

set = List @@ Range[5*^6]; (* unpacked *)

BenchmarkPlot[{Dispatch@Thread[# -> -1] &}, RandomChoice[set, #] &, 2^Range[22], 
 "IncludeFits" -> True, TimeConstraint -> 60]

enter image description here

The complexity of replacement itself however seems to increase with the number of rules:

set = List @@ Range[5*^6];  (* unpacked *)

BenchmarkPlot[{set /. # &}, Dispatch@Thread[# -> -1] &@RandomChoice[set, #] &, 
 2^Range[22], TimeConstraint -> 60]

enter image description here

Perhaps the constant-time floor is the cost to apply a single rule to the five million element list and it takes ~= 10^5 rules before the cost of checking that many rules rises above the floor. I'll try to think of other ways to probe this behavior.

$\endgroup$
  • $\begingroup$ +1 for using Dispatch and the BenchmarkPlot tool. It seems that I have to quit the kernel and re-run when removing Dispatch@ from fn2 (Even when I use ClearAll[fn2] before that. I suppose it has something to do with the internals of BenchmarkPlot ? $\endgroup$ – tchronis Jan 27 '15 at 6:24
  • $\begingroup$ @tchronis Thanks for the vote. BenchmarkPlot caches its results. I believe using slightly different input values will force a fresh run; for example you could use 2^Range[14] - 1. $\endgroup$ – Mr.Wizard Jan 27 '15 at 6:56
  • $\begingroup$ Thanks. Changing the name of one function also does the trick. Interesting! But is there a way to clear the cache? Anyway probably there are a lot of questions to be asked in only a comment. $\endgroup$ – tchronis Jan 27 '15 at 7:00
  • 2
    $\begingroup$ @tchronis I like questions or I wouldn't be on this site. ;-) A bit of spelunking suggests that this should do it: Clear[GeneralUtilities`Benchmarking`PackagePrivate`$TimingCaches] $\endgroup$ – Mr.Wizard Jan 27 '15 at 7:04
  • $\begingroup$ It would be interesting to know, if at extreme values of n, the gradients are the same; dispatch just has a much lower constant multiplier or dispatch's cost is growing slower regardless of n. $\endgroup$ – Ymareth Jan 27 '15 at 8:51
0
$\begingroup$

MapAt on alternatives pattern:

With[{
  list = {a, b, c, d, e, f, g, h},
  sub = {a, c, e, f}},
 MapAt[f, list,
  Position[list, Alternatives @@ sub]]]

Replace f with 12& for a constant-12 function.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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