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$
4
  • 5
    $\begingroup$ Try Range @@ sp (see also the Apply function). $\endgroup$
    – nben
    Commented May 17, 2017 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
    Commented May 17, 2017 at 19:02
  • $\begingroup$ Range @@ Replace[sp, All -> n, {1}] $\endgroup$
    – nben
    Commented May 17, 2017 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
    Commented May 17, 2017 at 19:55

2 Answers 2

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$
9
  • $\begingroup$ This will fail for (legal) spans like 1 ;; All ;; All or All ;; All ;; All. $\endgroup$
    – nben
    Commented May 17, 2017 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
    Commented May 17, 2017 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
    Commented May 17, 2017 at 19:39
  • $\begingroup$ @user16054 It fails for things like All;;5;;All when n is 10 (but so does mine). $\endgroup$
    – b3m2a1
    Commented May 17, 2017 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
    Commented May 17, 2017 at 19:50
3
$\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 and acknowledge you have read our privacy policy.

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