1
$\begingroup$

I want to call Range[] with its arguments depending on a condition. Say we have

checklength = {5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}  

I then want to call Range[] 13 times (the length of checklength) and do Range[5] when checklength[[#]] == 5 and Range[2, 6] when checklength[[#]] == 6. If[] would seem an appropriate way to do it,

Range[If[checklength[[#]] == 5, 5, XXX]]& /@ Range[13]  

but I don't know what to put for "XXX", since I need "2,6" there without any brackets. I've tried

Range[If[checklength[[#]]==5, 5, Flatten[{2,6}]]]& /@ Range[13]  

but that doesn't help (in fact if you think about it, it shouldn't!). The problem is, I need an unbracketed pair of numbers to be treated as a single argument and I don't know how to do that. I can think of one quite messy solution,

Range[If[checklength[[#]] == 5, 1, 2], If[checklength[[#]] == 5, 5, 6]& /@ Range[13]  

but I'd be disappointed if there's not a better way to do it. Even though this does the trick, the general question remains of how to treat unbracketed comma separated numbers as a single item.

$\endgroup$
2
  • 2
    $\begingroup$ A way without using If: checklength /. {5 -> Range@5, 6 -> Range[2, 6]} $\endgroup$
    – rm -rf
    Aug 12, 2013 at 15:48
  • $\begingroup$ @rm-rf Well don't I feel silly. (Didn't see that.) +1 on the comment. $\endgroup$
    – Mr.Wizard
    Aug 13, 2013 at 12:47

4 Answers 4

6
$\begingroup$

The solution you are asking for is to pass the sequence of 2,6 which is nicely defined as Sequence[2,6] However if we simply put that as the last argument in If, it will be misinterpreted, for instance If[False,Sequence[1,2]] would return 2. So we need to use Unevaluated:

Range[If[checklength[[#]] == 5, 5, Unevaluated[Sequence[2, 6]]]] & /@ Range[13]

However in your particular case I would question why you are not instead simply using:

If[checklength[[#]] == 5, Range[5], Range[2, 6]] & /@ Range[13]

or:

If[# == 5, Range[5], Range[2, 6]] & /@ checklength

Which seems less convoluted to interpret and does what you need.

$\endgroup$
4
  • $\begingroup$ The use of If on the outside instead of the inside, as you've suggested, is far more straight forward. Sometimes my brain picks a poor starting point and everything that follows is doomed! $\endgroup$
    – skratch
    Aug 12, 2013 at 15:48
  • 1
    $\begingroup$ Or perhaps Range[# - 4, #] & /@ checklength? $\endgroup$
    – Michael E2
    Aug 12, 2013 at 17:35
  • $\begingroup$ @MichaelE2. I did not see your comment. I have deleted my answer. $\endgroup$
    – user1066
    Aug 12, 2013 at 20:06
  • $\begingroup$ No problem. I don't mind either way. I thought my comment was just a variation on jVincent's answer. $\endgroup$
    – Michael E2
    Aug 12, 2013 at 20:18
4
$\begingroup$

For your "XXX" expression you need Sequence or an equivalent. But since If does not have the attribute SequenceHold it cannot appear explicitly as an argument of If or it will be flattened (inserted) prematurely. This was covered here and later here and here.

Therefore we may use ## &[2,6] since the head of this expression is not Sequence:

## &[2, 6] // FullForm // HoldForm
Function[SlotSequence[1]][2,6]
Range @ If[6 == 5, 5, ## &[2, 6]]
{2, 3, 4, 5, 6}

We could also use Unevaluated but there is no need to stack it on top of Sequence as it already evaluates to a Sequence expression.

Range @ If[6 == 5, 5, Unevaluated[2, 6]]
{2, 3, 4, 5, 6}

For your application it would IMO be simpler to use Apply instead and write:

Range @@ If[# == 5, {5}, {2, 6}] & /@ {5, 6}
{{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}}

Observe that I am mapping across the equivalent of checklength directly.

Still better is to avoid this If construct entirely and use replacement rules. Although Range is very fast and therefore the difference may not be significant this is also an order of magnitude faster on long lists than other general methods because the ranges are generated only once and then copied (thanks to the use of Rule rather than RuleDelayed).

{5, 5, 6} /. {5 -> Range[5], 6 -> Range[2, 6]}
{{1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}}

Note: You may notice red highlighting in the Front End in the expression Unevaluated[2, 6]; the multi-argument (and null-argument) form of Unevaluated is not documented but it is used internally by Mathematica. You can adjust the highlighting to match by setting:

Unprotect[Unevaluated];
SyntaxInformation[Unevaluated] = {"ArgumentsPattern" -> {___}};
Protect[Unevaluated];
$\endgroup$
3
  • $\begingroup$ I added a couple of solutions. They seem faster, but there's quite a variation between versions. Would you please check on V7? Thanks. $\endgroup$
    – Michael E2
    Aug 13, 2013 at 12:35
  • $\begingroup$ @MichaelE2 I confirm those are faster, but they're not general either, are they? I mean what if the ranges were not conveniently offset? Still I'll pull the wording from my answer. $\endgroup$
    – Mr.Wizard
    Aug 13, 2013 at 12:57
  • $\begingroup$ That's true about about the generality with respect to replacement ranges offsets & lengths. That might be worth pointing out explicitly. There's something to be said for ease of understanding, too, and the replacement method was the first I thought of and the easiest to understand, IMO. $\endgroup$
    – Michael E2
    Aug 13, 2013 at 13:06
3
$\begingroup$

Even simpler than mapping or Table, since Range is Listable:

Range[checklength - 4, checklength]

The above is elegant, but for the sake of speed, here's a faster one:

# + ConstantArray[Range[-4, 0], {Length@#}] &@ Developer`ToPackedArray @ checklist

yet this one is fastest:

Transpose @ Table[# + i, {i, -4, 0}] &@ Developer`ToPackedArray @ checklist

Timings

Comparing the rm-rf/Mr.Wizard replacement method with the two above:

n = 10^6;
cl = RandomInteger[{5, 6}, {10, n}]; (* ten trials *)
{
  Do[
     # + ConstantArray[Range[-4, 0], {Length@#}] &@
      Developer`ToPackedArray @ cl[[seed]], {seed, 10}] // Timing // First,
  Do[
     Transpose@Table[# + i, {i, -4, 0}] &@
      Developer`ToPackedArray @ cl[[seed]], {seed, 10}] // Timing // First,
  Do[
     cl[[seed]] /. {5 -> Range[5], 6 -> Range[2, 6]}, {seed, 10}] // Timing // First
  } / 10
         ConstantArray    Table     ReplaceAll
V9.0.1  {  0.0629536,  0.0469905,   0.1893714 }
V8.0.4  {  0.0297195,  0.0267913,   0.158208  }

Somewhat disappointing to see Mma slow down as it gets older. (This holds over data sizes for cl from 10^2 to 10^7.)

$\endgroup$
2
  • 1
    $\begingroup$ @TomD We were both just one character away from this: Range[# - 4, #] &@ checklength (@ instead of /@). :) $\endgroup$
    – Michael E2
    Aug 12, 2013 at 21:02
  • $\begingroup$ Nice one. Not a character of no importance :) $\endgroup$
    – user1066
    Aug 13, 2013 at 10:15
1
$\begingroup$

Another straightforward approach is to just build the data using a Table. No If decisions required.

Table[Range[checklength[[i]] - 4, checklength[[i]]], {i, 1, Length[checklength]}]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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