3
$\begingroup$

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.

$\endgroup$
  • 5
    $\begingroup$ Try Range @@ sp (see also the Apply function). $\endgroup$ – nben May 17 '17 at 18:57
  • 1
    $\begingroup$ @user16054: one problem: there are cases where this conversion requires knowledge of n... E.g. when sp = 3;;. $\endgroup$ – kjo May 17 '17 at 19:02
  • $\begingroup$ Range @@ Replace[sp, All -> n, {1}] $\endgroup$ – nben May 17 '17 at 19:19
  • 1
    $\begingroup$ (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}] $\endgroup$ – nben May 17 '17 at 19:55
2
$\begingroup$

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}
$\endgroup$
  • $\begingroup$ This will fail for (legal) spans like 1 ;; All ;; All or All ;; All ;; All. $\endgroup$ – nben May 17 '17 at 19:29
  • $\begingroup$ 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. $\endgroup$ – b3m2a1 May 17 '17 at 19:30
  • $\begingroup$ 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. $\endgroup$ – nben May 17 '17 at 19:39
  • $\begingroup$ @user16054 It fails for things like All;;5;;All when n is 10 (but so does mine). $\endgroup$ – b3m2a1 May 17 '17 at 19:43
  • 1
    $\begingroup$ @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]] $\endgroup$ – b3m2a1 May 17 '17 at 19:50
2
$\begingroup$

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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