# How to convert span to flat list of indices?

Let sp be a Span object, and n a non-negative integer.

I'm looking for an efficient way to generate the list ii of indices such that the expressions

x[[sp]]
x[[ii]]

produce identical results, for all lists x of length n.

E.g., the desired conversion for sp = 3;;7 and n = 7 would be

{3, 4, 5, 6, 7}

One generally very inefficient (but admirably straightforward) way to generate the list ii is with the expression

Range[n][[sp]]

Conceptually it is not difficult to envision how to compute such a mapping, but a full implementation would be tedious, since Span has so many variants.

• Try Range @@ sp (see also the Apply function). – nben May 17 '17 at 18:57
• @user16054: one problem: there are cases where this conversion requires knowledge of n... E.g. when sp = 3;;. – kjo May 17 '17 at 19:02
• Range @@ Replace[sp, All -> n, {1}] – nben May 17 '17 at 19:19
• (See comments in MB1965's answer below); to get correct behavior when the 3rd argument is All: Range @@ Replace[ If[Length[s] == 3 && s[[3]] === All, ReplacePart[s, 3 -> -1], s], All -> n, {1}] – nben May 17 '17 at 19:55

Here's a quick example of how you could build it without using Apply:

spanConvert[n_Integer,
Span[start : _Integer | All : 1,
end : _Integer | All : All,
step : _Integer | All : 1]] :=
With[{
s = Replace[Replace[start, All -> n], i_?Negative :> (n - i)],
e = Replace[Replace[end, All -> n], i_?Negative :> (n - i)]
},
With[{p = Replace[step, All -> If[s <= e, 1, -1]]},
Range[s, e, p]
]
];
spanConvert[list_, span_Span] := spanConvert[Length@list, span];
spanConvert /: Part[x_, spanConvert[span_Span]] :=

spanConvert[x, span]

Then:

x[[spanConvert[1 ;; ;; 2]]]

will dump the parts being taken, e.g.:

In[83]:= RandomReal[1, 10][[spanConvert[1 ;; ;; 2]]]

Out[83]= {1, 3, 5, 7, 9}
• This will fail for (legal) spans like 1 ;; All ;; All or All ;; All ;; All. – nben May 17 '17 at 19:29
• What does a step of All mean? If it's just one that's a trivial replacement. And I assume the All at the beginning just maps back to 1 so that's trivial too. – b3m2a1 May 17 '17 at 19:30
• No, it's a bit more complicated. ;; All gets converted to 1 ;; All on input, but a span All ;; All is valid and is equivalent to n ;; n. I believe my comment on the original post (Range @@ Replace[sp, All -> n, {1}]) reproduces the correct values. – nben May 17 '17 at 19:39
• @user16054 It fails for things like All;;5;;All when n is 10 (but so does mine). – b3m2a1 May 17 '17 at 19:43
• @user16054 it seems the end parameter is just converted to a -1. This throws the same error: Range[10][[1 ;; -1 ;; All]]. All converts itself to a -1 as the end is smaller than the start. See: Range[10][[1 ;; -1 ;; -1]] – b3m2a1 May 17 '17 at 19:50

Here is a souped up version of @b3m2a1's answer that supports UpTo, corrects a couple bugs, and adds error messages. I used the name Inspantiate because I couldn't resist (probably something like FromSpan would be better):

Inspantiate[max_, span_Span] := Module[{res = Catch[iList[max, span], "SpanFailure"]},
res /; res =!= $Failed ] iList[max_, span:Span[a_, b_, c_:1]] := Module[{x, y, z}, x = Replace[a, { n_Integer :> If[Abs@n>max || n==0, Message[Inspantiate::take, a, b, HoldForm[Range[1,max]]]; Throw[$$Failed, "SpanFailure"], Mod[n, max+1] ], All -> 1, UpTo[n_Integer?Positive] :> Min[n,max], _ :> (Message[Inspantiate::span, span]; Throw[$$Failed, "SpanFailure"]) } ]; y = Replace[b, { n_Integer :> If[Abs@n>max || n==0, Message[Inspantiate::take, a, b, HoldForm[Range[1,max]]]; Throw[$$Failed, "SpanFailure"], Mod[n, max+1] ], All -> max, UpTo[n_Integer?Positive] :> Min[n,max], _ :> (Message[Inspantiate::span, span]; Throw[$$Failed, "SpanFailure"]) } ]; z = Replace[c, { All -> If[x<=y, 1, -1], Except[_Integer] :> (Message[Inspantiate::span, span]; Throw[$Failed, "SpanFailure"])
}
];
Range[x,y,z]
]

Bug fixes:

spanConvert[10, All;;All]
Inspantiate[10, All;;All]

Range[10][[All;;All]]

{10}

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

spanConvert[10, -5;;]
Inspantiate[10, -5;;]

Range[10][[-5;;]]

{}

{6, 7, 8, 9, 10}

{6, 7, 8, 9, 10}

Error messages:

Inspantiate[3, 5;;7]
Range[3][[5;;7]]

Inspantiate::take: Cannot take positions 5 through 7 in Range[1,3].

Inspantiate[3, 5 ;; 7]

Part::take: Cannot take positions 5 through 7 in {1,2,3}.

{1, 2, 3}[[5 ;; 7]]

If you come across a Span specification where Inspantiate behaves differently from Range[span], please let me know.