Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

Span (;;) is very useful, but doesn't work with a lot of functions. Given the following input

list = {{"a", "b", "c"}, {"d", "e", "f", 
   "g"}, {"h", {{"i", "j"}, {"k", "l"}, {"m", "n"}, {"o", "pp"}}}}

We would like

MapAt[Framed, list, 1 ;; 2]
MapAt[Framed, list, {{1, 1}, {2, 2 ;; 3}, {3, 2, 1 ;; 3, 1}}]

to work as expected

enter image description here

Here is my first go at it:

SpanToRange[Span[x_:1,y_:1,z_:1]] := Module[{zNew = z},
    If[x>y && z==1, zNew = -1];
        Range[x, y, zNew]
    ] /; And[VectorQ[{z,y,z}, IntegerQ],
    And @@ Thread[{z,y,z} != 0]]

helper = Function[list,
    Module[{li=list},
        If[FreeQ[li, Span], li,
        li = Replace[li,s_ /; Head[s] =!= Span :> {s}, {1}];
        li = li /. s:_Span :> SpanToRange[s];
        Sequence @@ Flatten[
            Outer[List, Sequence @@ li],
            Depth[Outer[List, Sequence @@ li]]-3]]
    ]
];

protected = Unprotect[Span, MapAt];
Span /: MapAt[func_, list_, s:Span[x_:1,y_:1,z_:1]]:= MapAt[func, 
 list, Thread[{SpanToRange[s]}]];
MapAt[func_, list_, partspec_] /; !FreeQ[partspec, Span] := Module[{f,p = partspec},
    MapAt[func, list, Join[helper /@ p]]
];
Protect[Evaluate[protected]];

But this is far from finished, and the extended down values should support all valid uses of Span such as

MapAt[Framed, list, 3 ;;]
MapAt[Framed, list, ;; ;; 2]
MapAt[Framed, list, ;; 10 ;; 2]
share|improve this question
    
I meant to ask this very same question a week ago, but it fell by the way side, +1. –  rcollyer May 29 '12 at 1:26
add comment

4 Answers

up vote 9 down vote accepted

I propose using Part instead:

list = {{"a", "b", "c"}, {"d", "e", "f", "g"},
         {"h", {{"i", "j"}, {"k", "l"}, {"m", "n"}, {"o", "pp"}}}};

mapAtSpan[func_, list_, x : Except[_List]] := mapAtSpan[func, list, {{x}}]
mapAtSpan[func_, list_, spec_] :=
 Module[{A = list, f},
   f[x_List] := f /@ x;
   f[x_] := func[x];
   (Part[A, ##] = f@Part[A, ##]) & @@@ Flatten /@ List /@ spec;
   A
 ]

mapAtSpan[Framed, list, {{1, 1}, {2, 2 ;; 3}, {3, 2, 1 ;; 3, 1}}]

Mathematica graphics

Using the same idea

SpannishMapAt[fun_, expr_, {p : Except[_List]} | p : Except[_List]] :=
   SpannishMapAt[fun, expr, {{p}}];
SpannishMapAt[fun_, expr_, p : {{__} ..}] := Block[{A = expr},
  Do[
   A[[Sequence @@ i]] = 
    Map[fun, A[[Sequence @@ i]], {Count[i, _Span | All]}], {i, p}];
  A
  ]
share|improve this answer
    
Definately smarter +1 –  Rojo May 28 '12 at 8:32
    
@Rojo actually no, not in its present form. I just realize that my cheap hack making f pseudo-listable breaks this for mapAtSpan[Framed, list, 1 ;; 2] because the parts are lists. I'm not feeling inspired and I'm doing something else right now; maybe you can fix it, or take my idea and do it properly. –  Mr.Wizard May 28 '12 at 8:42
add comment

There is a hidden update in V9: MapAt works with Span.

I've checked it does not work on V8 and V7.

enter image description here

I just started to do this once in the past and it worked. I was newbie in Mathematica when there was V8 or V7 so I have not realised it is new till Mr. Wizard poited out in comments that I'm smoking crack :).

I do not remember other case but it is the second, which I can recall, where there is no mark about this in documentation. I do not mean examples, I mean there is no "Last modyfied in 9" for MapAt only "New in 1.".

Couple of examples where I've used it:


I strongly recommend this, it is so handy, and, as Mr. Wizard noticed, fast!

big = Range[1*^5];
First@Timing@MapAt[#^2 &, big, List /@ Range[30000, 40000]] 
First@Timing@MapAt[#^2 &, big, 30000 ;; 40000]
10.202465
0.015600

Extended comparision inspired by RunnyKine:

test = {};
Do[ big = Range[10^i];
    AppendTo[test,
             {i,
              Mean@Last@Last@Reap@Do[
                  Sow@First@Timing@MapAt[#^2 &, big, List /@ Range[3000, 4000]], {10}],
              Mean@Last@Last@Reap@Do[
                  Sow@First@Timing@MapAt[#^2 &, big, 3000 ;; 4000], {10}]
             }]
  , {i, 5, 6.4, .2}]

ListLogPlot[Transpose[test][[2 ;;]], Joined -> True, DataRange -> {5, 6.4}]

enter image description here

share|improve this answer
1  
Oh great, now my "smoking crack" comment is immortalized. :-p –  Mr.Wizard Aug 26 '13 at 20:52
    
By the way, I do read other people's code, especially concise code which you often write. Since I expected news such as Span working in MapAt to be heralded I probably just assumed you had coded that without actually testing it, and I was too lazy to ask. –  Mr.Wizard Aug 26 '13 at 22:14
    
@Mr.Wizard I see, well just a coincidence that I've though it is normal thing to use Span :) –  Kuba Aug 26 '13 at 22:16
7  
It also works with All, e.g. fakedailydata = Transpose@{DatePlus[{2001, 1}, #] & /@ Range[0, 999], Accumulate[RandomVariate[NormalDistribution[0, 1], {1000}]]}; MapAt[#^2 &, fakedailydata, {All, 2}]. Really handy for time series (date-value pairs). –  Verbeia Aug 27 '13 at 0:10
    
Actually, I look at code. For me, the weirder the better, so Mr.W's often matches well for me. This, however, I just noticed. +1 –  rcollyer Aug 27 '13 at 0:48
show 8 more comments

Perhaps

myMapAt[f_, exp_, pos : {__List}] := 
 MapAt[f, exp, 
  Replace[pos, All :> ;;, {2}] //. {bef___, Span[s__], aft___} :> 
    Sequence @@ 
     Thread@{bef, 
       Range @@ ({s} /. 
          With[{l = Length[exp[[bef]]]}, {All :> l, 
            i_?Negative :> l + i + 1}]), aft}]

myMapAt[f_, exp_, pos_List] := myMapAt[f, exp, {pos}];
myMapAt[f_, exp_, pos_] := myMapAt[f, exp, {{pos}}];

This is a question about joining Span and MapAt. This approach reinvents Span and uses MapAt. See MrWizard's solution for a version that reinvents MapAt and uses Span

share|improve this answer
    
Very close, but what about these cases like: myMapAt[Framed, {1, 2, 3, 4, 5}, 3 ;; -2 ;;] –  M.R. May 28 '12 at 7:20
    
Forgot about them :). Try –  Rojo May 28 '12 at 7:27
add comment

Not very pretty, but you could try something like this

mapAt[f_, exp_, index_Integer] := MapAt[f, exp, index]
mapAt[f_, exp_, List[index__Integer]] := MapAt[f, exp, index]
mapAt[f_, exp_, a_Span] := MapAt[f, exp, Thread[{Range[Length[exp]][[a]]}]]

mapAt[f_, exp_, b : {(_Integer | _Span) ..}] := Module[{rlist, pos},
  pos = Flatten@Position[b, _Span];
  rlist = Fold[
    Function[{prev, p},
     ArrayFlatten[ReplacePart[#, p -> Thread[{Range[Length[
               If[p > 1, 
                Extract[exp, #[[;; p - 1]]], 
                exp]]][[b[[p]]]]}]] & /@ prev]], {b}, pos];
  MapAt[f, exp, rlist]]

mapAt[f_, exp_, b : {{(_Integer | _Span) ..} ..}] :=
 Module[{rlist},
  rlist = Flatten[
    Function[bsub,
      Module[{pos},
       pos = Flatten[Position[bsub, _Span]];
       Fold[Function[{prev, p},
         ArrayFlatten[ReplacePart[#, p -> Thread[{Range[Length[
                   If[p > 1, 
                    Extract[exp, #[[;; p - 1]]], 
                    exp]]][[bsub[[p]]]]}]] & /@ prev]],
        {bsub}, pos]]] /@ b, 1];
  MapAt[f, exp, rlist]]

For the example in the original question mapAt returns

list = {{"a", "b", "c"}, {"d", "e", "f", 
   "g"}, {"h", {{"i", "j"}, {"k", "l"}, {"m", "n"}, {"o", "pp"}}}}

mapAt[Framed, list, ;; 2]
mapAt[Framed, list, {{1, 1}, {2, 2 ;; 3}, {3, 2, 1 ;; 3, 1}}]

Mathematica graphics

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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