Tell me more ×
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

3 Answers

up vote 3 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
I just did a version and edited. I think it works – Rojo May 28 '12 at 23:24
@Rojo thanks! I'll take a look at it. – Mr.Wizard May 29 '12 at 0:56

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

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

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.