1

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.

Michael E2
  • 235,386
  • 17
  • 334
  • 747
skratch
  • 117
  • 7

4 Answers4

6

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.

jVincent
  • 14,766
  • 1
  • 42
  • 74
4

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];
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • I added a couple of solutions. They seem faster, but there's quite a variation between versions. Would you please check on V7? Thanks. – Michael E2 Aug 13 '13 at 12:35
  • @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. – Mr.Wizard Aug 13 '13 at 12:57
  • 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. – Michael E2 Aug 13 '13 at 13:06
3

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.)

Michael E2
  • 235,386
  • 17
  • 334
  • 747
1

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]}]
bill s
  • 68,936
  • 4
  • 101
  • 191