# How to make MapAt work with Span?

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

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],

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,
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]

-
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

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}}]


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
]

-
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

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

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

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}]


-
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
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
@Kuba, just noticed this earlier Q/A using the same hidden v9 update:) –  kglr May 24 '14 at 9:06

Perhaps

myMapAt[f_, exp_, pos : {__List}] :=
MapAt[f, exp,
Replace[pos, All :> ;;, {2}] //. {bef___, Span[s__], aft___} :>
Sequence @@
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

-
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

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},
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},
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}}]


-