3

In Mathematica, what is the cleanest way of taking a list

{r1, r2, r3, ..., rn, a, b}

and returning

{r1, r2, r3, ..., rn, a + b}

or more generally

{r1, r2, r3, ..., rn, f[a, b]}

for some function f?

Simon
  • 14,631
  • 4
  • 41
  • 101
  • Simon, I expected a beginner to be asking this question, not you. What precisely are you after? Let me rephrase that: by what metric will you evaluate the replies? Execution speed? Shortest code? Most easily readable to Mathematica novices? – Mr.Wizard Apr 12 '11 at 06:46
  • @Mr.Wizard: Just the cleanest/clearest code. I came up with a few options, but wasn't happy with most of them. So I thought that maybe someone else has a better idea. And they did - for some reason I had completely blanked on using `ReplaceAll`! – Simon Apr 12 '11 at 06:53
  • @Mr.Wizard: Anyway - I thought it might be a nice simple/fun question after all of the murky digging in Mma's internals that has been going on! – Simon Apr 12 '11 at 06:54
  • +1, for making me think I had a simple solution, `MapAt`, and then discovering that it clearly did not do what you asked for ... – rcollyer Apr 12 '11 at 13:15

5 Answers5

3

I'd use rules if performance is not a big issue (lists are not packed etc):

 lst = {a, b, c, d, e}

 In[13]:= Replace[lst, {left__, ntl_, l_} :> {left, f[ntl, l]}, {0}]

 Out[13]= {a, b, c, f[d, e]}
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Leonid, is the last part of your code: `, {0}` doing anything? – Mr.Wizard Apr 12 '11 at 07:39
  • 1
    @Mr.Wizard Yes, it does. It makes sure that the replacement is only done on expression itself and not any of its parts. I find this a useful idiom in many cases, when you want to bullet-proof your rule-based solutions. In comparison, `ReplaceAll` often leads to bugs when rules are matched on a different level of expression than planned. – Leonid Shifrin Apr 12 '11 at 07:46
  • But `Replace` already has that property, does it not? `Replace[{1, 2, 3}, _Integer -> "x"]` – Mr.Wizard Apr 12 '11 at 07:47
  • @Mr.Wizard Apparently you are right, I just checked the docs. I often use it with a different level spec, like `{1}` or `{2}`, so I probably got a habit of using it explicitly. Unnecessary here, indeed. – Leonid Shifrin Apr 12 '11 at 07:52
3
lst = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a, b}; 
lst /. {a___, b_, c_} -> {a, f[b, c]}

 ==> {a[1], a[2], a[3], a[4], a[5], a[6], a[7], f[a, b]}

or (ugly):

Append[Take[lst, {1, -3}], f @@ lst[[{-2, -1}]]]
Sjoerd C. de Vries
  • 16,122
  • 3
  • 42
  • 94
  • Thanks Sjoerd! The first solution probably wants to use `RuleDelayed` instead of `Rule`. Also the last one can be slightly prettified as `Append[lst[[1;;-3]], f@@lst[[-2;;-1]]]`. (of course, beauty is in the eye of the beholder and all that) – Simon Apr 12 '11 at 07:06
  • btw: I had completely blanked about `ReplaceAll` when looking at this problem, so all of my solutions were like your second (ugly) one. – Simon Apr 12 '11 at 07:24
  • Interestingly your "ugly" method is a little faster than mine, but I thought (in the past) I tested similar operations and found the form I posted to be faster. I guess not. Maybe I was able to do in-place manipulation at that time. – Mr.Wizard Apr 12 '11 at 07:32
  • You get the "tick" since `/.` is visually the clearest and your "ugly" solution is (according to @Mr.W) the fastest! – Simon Apr 12 '11 at 22:34
1

If I hadn't second guessed Simon I would have been first to answer. Nuts. Anyway, here is my late-to-the-party reply.

combineLast =
  Module[{x = #},
    x[[-2]] = #2 @@ x[[-2 ;;]];
    Most[x]
  ] &;

Comparison:

leoCL[lst_, f_] := Replace[lst, {left__, ntl_, l_} :> {left, f[ntl, l]}, {0}]

a = RandomInteger[1*^9, 5000];

Do[combineLast[a, Plus], {5000}] // Timing
Do[leoCL[a, Plus], {5000}] // Timing
{0.078, Null}

{1.844, Null}
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • Wow! Your computer is MUCH faster than mine! If you want a little more speed, then the list slicing `[[-2;;]]` can be replaced with the more explicit `[[{-2, -1}]]`. – Simon Apr 12 '11 at 07:11
  • @Simon, it shouldn't be faster; this is an old machine using an entry-level processor. What version of Mathematica are you using? I am not surprised if `[[{-2, -1}]]` is faster, but my love of terseness won. – Mr.Wizard Apr 12 '11 at 07:18
  • I'm running `8.0.1.0 for Linux x86 (64-bit)` on a dual core AMD Turion 64 X2 in a low-end HP laptop. – Simon Apr 12 '11 at 07:21
  • @Simon I wonder why this is slow on your machine. I just tried `[[{-2, -1}]]` and it is too close to call, another indication that behavior differs. I seem to recall someone on MathGroup complaining that Mma 8 was slow. Is is possible for you to try this operation on Mma 7? Did you do any speed comparisons when you got Mma 8? – Mr.Wizard Apr 12 '11 at 07:24
  • @Simon @Mr. It's twice as fast as on my brand new i7 laptop 8-( (mma v8) – Sjoerd C. de Vries Apr 12 '11 at 07:25
  • @Sjoerd (sorry, can't read, ignore first question) And do you mean twice as fast as my timings? – Mr.Wizard Apr 12 '11 at 07:26
  • @Mr.Wizard Can't read which question? Anyway, your timings are twice as fast as mine. – Sjoerd C. de Vries Apr 12 '11 at 07:34
  • @Mr.Wizard: The v7 and v8 timings are almost identical. Though it's strange that when I first ran your `combineLast` code your machine was about 6 times faster and the `leoCL` code took forever. Now `combineLast` and `leoCL` are 2.3 and 2.9 times faster on your machine... – Simon Apr 12 '11 at 07:36
  • @Sjoerd sorry for the confusion. I had asked you what version you were running, before realizing that you already said mma v8, so I edited out that part. – Mr.Wizard Apr 12 '11 at 07:41
  • @Simon @Sjoerd, I find it strange that my old machine outperforms yours. Now I am curious. I am going to run a benchmark and post the results. If either of you can do the same in Mathematica 7, I would like to see the numbers. Maybe my machine is only fast on a few operations. – Mr.Wizard Apr 12 '11 at 07:43
  • @Mr.Wizard I left MMA 7 on my previous laptop, and am running mma 8 on my new one. So I guess it doesn't make sense for me to run both. – Sjoerd C. de Vries Apr 12 '11 at 07:52
  • @Mr.Wizard: Got closed way to quick! Probably best to post it on the [Mma tag chat](http://chat.stackoverflow.com/rooms/628/mathematica-tag). – Simon Apr 12 '11 at 07:57
  • Interesting to me: [[-2;;]] --- Can't recall ever coming across that syntax before. @Simon -- What would make [[-2;;]] slower? – telefunkenvf14 Apr 12 '11 at 12:18
  • @tele that uses `Span`. See: http://reference.wolfram.com/mathematica/ref/Span.html – Mr.Wizard Apr 12 '11 at 12:20
  • @telefunkenvf14 I didn't see your edit the first time. In my limited testing using Span was not significantly slower in practical cases. It is a call to another function, rather than direct input to part, so in cases of many applications to a short list, it is slightly slower. `a = Range[5];` Then `First@Timing@Do[a[[-2 ;;]], {5000000}]` is a little slower than `First@Timing@Do[a[[{-2, -1}]], {5000000}]` but again, one is usually working with longer lists. – Mr.Wizard Apr 12 '11 at 14:05
1

Suppose 'list' is defined:

Remove[list];
list = {r1, r2, r2, r4, r5, a, b};

Re-set 'list' to be {r1, r2, r3, r4, r5, a} with the [[-1]] replaced by the sum of the last two elements in 'list'.

list = ReplacePart[Drop[list, -1], -1 -> Plus @@ list[[-2 ;; -1]]]

Thanks for asking this, btw. :)

telefunkenvf14
  • 1,011
  • 7
  • 19
1

Here is my take on it:

addLastTwo = Function[Append[Drop[#, -2], Total[Take[#, -2]]]];

In[225]:= addLastTwo[{r1, r2, r3, r4, r5}]

Out[225]= {r1, r2, r3, r4 + r5}

This is slightly faster than Mr.Wizard's solution, although less general:

In[226]:= Do[addLastTwo@a, {10000}] // Timing

Out[226]= {0.25, Null}

In[227]:= Do[combineLast[a, Plus], {10000}] // Timing

Out[227]= {0.39, Null}
Sasha
  • 5,935
  • 1
  • 25
  • 33