Ranges

A range is a first-class, constant-space representation of a regular sequence of values. These values are typically integers, though ranges over bools, enums, and other types are also supported. Ranges support serial and parallel iteration over the sequence of values they represent, as well as operations such as counting, striding, intersection, shifting, and comparisons. Ranges form the basis for defining rectangular domains (Domains) and arrays (Arrays) in Chapel.

Ranges are presented as follows:

Range Concepts

A range has four primary properties. Together they define the sequence of indices that the range represents, or the represented sequence, as follows.

  • The low bound is either a specific index value or -\(\infty\).

  • The high bound is either a specific index value or +\(\infty\). The low and high bounds determine the span of the represented sequence. Chapel does not represent \(\infty\) explicitly. Instead, infinite bound(s) are represented implicitly in the range’s type (Range Types). When the low and/or high bound is \(\infty\), the represented sequence is unbounded in the corresponding direction(s). However, when indices are drawn from a finite type, such as booleans or enum constants, the represented sequence always terminates when it runs out of legal indices of this type.

  • The stride is a non-zero integer. It defines the distance between any two adjacent members of the represented sequence. The sign of the stride indicates the direction of the sequence:

    • \(stride > 0\) indicates an increasing sequence,

    • \(stride < 0\) indicates a decreasing sequence.

  • The alignment is either a specific index value or is ambiguous. It defines how the represented sequence’s members are aligned relative to the stride. The alignment, when unambiguous, is always between zero and \(|stride|-1\), inclusively.

A range with unambiguous alignment is called aligned. Otherwise the range is unaligned and its represented sequence is undefined.

The represented sequence for an aligned range \((low, high, stride, alignmt)\) with integral indices contains all indices \(ix\) such that:

\(low \leq ix \leq high\) and \(ix \equiv alignmt \pmod{|stride|}\)

If the represented sequence is defined but there are no indices satisfying the above equation, the range and its represented sequence are empty. A common case of this occurs when the low bound is greater than the high bound.

We say that a value \(ix\) is aligned w.r.t. the range \((low, high, stride, alignmt)\) if:

\(alignmt\) is not ambiguous and \(ix \equiv alignmt \pmod{|stride|}\).

Furthermore, \(\infty\) is never aligned.

Ranges have the following additional properties.

  • The first index is the first member of the represented sequence.

    A range has no first index when the first member is undefined, that is, in the following cases:

    • the range is unaligned,

    • the represented sequence is empty,

    • the represented sequence is increasing and the low bound is -\(\infty\),

    • the represented sequence is decreasing and the high bound is +\(\infty\).

  • The last index is the last member of the represented sequence.

    A range has no last index when the last member is undefined, that is, in the following cases:

    • the range is unaligned,

    • the represented sequence is empty,

    • the represented sequence is increasing and the high bound is +\(\infty\),

    • the represented sequence is decreasing and the low bound is -\(\infty\).

  • The aligned low bound is the smallest value that is greater than or equal to the low bound and is aligned w.r.t. the range, if such a value exists.

    The aligned low bound equals the smallest member of the represented sequence, when both exist.

  • The aligned high bound is the largest value that is less than or equal to the high bound and is aligned w.r.t. the range, if such a value exists.

    The aligned high bound equals the largest member of the represented sequence, when both exist.

  • The range is iterable, that is, it is legal to iterate over it, if it has a first index.

Range Types

The type of a range is characterized by three properties:

  • idxType is the type of the values in the range’s represented sequence. However, when the range’s represented sequence is infinite, it also contains indices that are not representable by idxType.

    idxType must be an integral, boolean, or enumerated type and is int by default. The range’s low bound and high bound (when they are not \(\infty\)) and alignment are of the type idxType. The range’s stride is of the signed integer type that has the same bit size as idxType for integral ranges; for boolean and enumerated ranges, it is simply int.

  • bounds indicates which of the range’s bounds are not \(\infty\). bounds is an enumeration constant of the type boundKind. It is discussed further below.

  • strides indicates what values of stride are allowed for this type. strides is an enumeration constant of the type strideKind. It is discussed further below.

bounds is one of the constants of the following enumeration:

enum boundKind { both, low, high, neither };

The value of bounds determines which bound(s) of the range are specified (making the range “bounded”, as opposed to infinite, in the corresponding direction(s)) as follows:

  • both: both bounds are specified. Such ranges are called bounded.

  • low: the low bound is specified, the high bound is +\(\infty\).

  • high: the high bound is specified, the low bound is -\(\infty\).

  • neither: neither bound is specified, both bounds are \(\infty\). Such ranges are called unbounded.

bounds is boundKind.both by default.

strides is one of the constants of the following enumeration:

enum strideKind { one, negOne, positive, negative, any };

The value of strides determines what values of stride this range can have as follows:

  • one: stride must be \(1\).

  • negOne: stride must be \(-1\).

  • positive: stride must be positive.

  • negative: stride must be negative.

  • any: stride can take on any value other than zero.

strides is strideKind.one by default.

The parameters idxType, bounds, and strides affect all values of the corresponding range type. For example, the range’s low bound is -\(\infty\) if and only if the bounds of that range’s type is either high or neither.

Rationale.

Providing bounds and strides in a range’s type allows the compiler to identify and optimize the common cases where the range is bounded in both directions and/or its stride is 1.

A range type has the following syntax:

range-type:
  'range' ( named-expression-list )

That is, a range type is obtained as if by invoking the range type constructor (The Type Constructor) that has the following header:

proc range(type idxType  = int,
           param bounds  = boundKind.both,
           param strides = strideKind.one) type

As a special case, the keyword range written without a parenthesized argument list refers to the concrete range type with the default values of all its parameters, i.e., range(int, boundKind.both, strideKind.one).

Example (rangeVariable.chpl).

The following declaration declares a variable r that can represent ranges of 32-bit integers, with both low and high bounds specified, and the ability to have a stride other than 1.

var r: range(int(32), boundKind.both, strides=strideKind.any);

Range Values

A range value consists of the range’s four primary properties (Range Concepts): low bound, high bound, stride and alignment.

Range Literals

Range literals are specified with the following syntax.

range-literal:
  expression .. expression
  expression ..< expression
  expression ..
  .. expression
  ..< expression
  ..

The expressions to the left and to the right of .. or ..<, when given, are called the lower bound expression and the upper bound expression, respectively. The .. operator defines a closed-interval range, whereas the ..< operator defines a half-open interval.

The type of a range literal is a range with the following parameters:

  • idxType is determined as follows:

    • If both the lower bound and the upper bound expressions are given and have the same type, then idxType is that type.

    • If both the lower bound and the upper bound expressions are given and an implicit conversion is allowed from one expression’s type to the other’s, then idxType is that type.

    • If only one bound expression is given and it has an integral, boolean, or enumerated type, then idxType is that type.

    • If neither bound expression is given, then idxType is int.

    • Otherwise, the range literal is not legal.

  • bounds is a value of the type boundKind that is determined as follows:

    • both, if both the lower bound and the upper bound expressions are given,

    • low, if only the upper bound expression is given,

    • high, if only the lower bound expression is given,

    • neither, if neither bound expression is given.

  • strides is strideKind.one.

The value of a range literal is as follows:

  • The low bound is given by the lower bound expression, if present, and is -\(\infty\) otherwise.

  • When the range has an upper bound expression, a closed-interval range (..) takes the expression’s value as its high bound; whereas the high bound of a half-open interval range (..<) excludes the upper bound and is one less than the upper bound expression. If there is no upper bound expression, the high bound is +\(\infty\).

  • The stride is 1.

  • The alignment is 0.

Default Values

The default value for a range with an integral idxType depends on the type’s bounds parameter as follows:

  • 1..0 (an empty range) if bounds is both

  • 1.. if bounds is low

  • ..0 if bounds is high

  • .. if bounds is neither

Rationale.

We use 0 and 1 to represent an empty range because these values are available for any integer idxType with more than one value.

We have not found the natural choice of the default value for ranges with low and high bounds. The values indicated above are distinguished by the following property. Slicing the default value for a low-bounded range with the default value for a high-bounded range (or visa versa) produces an empty range, matching the default value for a both-bounded range

Default values of ranges with boolean idxType are similar, but substituting false and true for 0 and 1 above. Ranges with enum idxType use the 0th and 1st values in the enumeration in place of 0 and 1 above. If the enum only has a single value, the default value uses the 0th value as the low bound and has an undefined high bound; the .size query should be used with such ranges before querying the high bound to determine whether or not it is valid.

Warning

Default initialization of ranges with boundKind.low or boundKind.high is unstable w.r.t. the value of their finite bound.

Common Operations

All operations on a range return a new range rather than modifying the existing one. This supports a coding style in which all range values are immutable.

Rationale.

The intention is to provide ranges as immutable objects.

Immutable objects may be cached without creating coherence concerns. They are also inherently thread-safe. In terms of implementation, immutable objects are created in a consistent state and stay that way: Outside of initializers, internal consistency checks can be dispensed with.

These are the same arguments as were used to justify making strings immutable in Java and C#.

Range Assignment

Assigning one range to another results in the target range copying the low and high bounds, stride, and alignment from the source range.

Range assignment is legal when:

  • An implicit conversion is allowed from idxType of the source range to idxType of the destination range type,

  • the two range types have the same bounds, and

  • the strides parameter of the destination range is the same or more permissive than that of the source range.

Warning

The ability to assign between two unbounded ranges with incompatible idxTypes is deprecated.

Range Comparisons

Ranges can be compared using equality and inequality.

Warning

Equality comparisons currently treat ranges over enum or bool types as bounded on both ends regardless of their bounds parameters. This behavior is unstable and might change in the future.

operator ==(r1: range(?), r2: range(?)) : bool

Returns true if the two ranges have the same represented sequence or the same four primary properties, and false otherwise.

operator !=(r1: range(?), r2: range(?)) : bool

Returns false if the two ranges have the same represented sequence or the same four primary properties, and true otherwise.

Iterating over Ranges

A range can be used as an iterator expression in a loop. This is legal only if the range is iterable. In this case, the loop iterates over the members of the range’s represented sequence in the order defined by the sequence. If the range is empty, no iterations are executed.

Overflow of the index variable while iterating over an unbounded range leads to undefined behavior. For unbounded ranges of bool or enum index type, the iteration will stop at the last value represented by the type.

In order for it to be possible to iterate over a range with a last index, it needs to be possible to add the stride to the range’s last index without overflowing the index type. In other words, the last index plus the stride must be between the index type’s minimum and maximum value (inclusive). If this property is not met, the program will have undefined behavior.

Implementation Notes.

When bounds checking is enabled, the case in the above paragraph is checked at runtime and the program will halt if the range iteration is invalid.

Iterating over Unbounded Ranges

When an unbounded range of integer values is used to drive a loop, either by being the only iterand, or by serving as the leader iterand of a zippered iteration, it will generate a conceptually infinite number of iterations. In order for such loops to be useful in practice, they must typically contain a break or return statement. Of course, in practice, the values representable by idxType are finite; as a result, when the loop reaches its extreme values, the behavior of the loop is undefined.

Implementation Notes.

In the current implementation of Chapel, the loop will halt with an error once it yields a value within stride of the maximal idxType value.

When an unbounded range of integer values serves as a follower iterand in a zippered context ( Zippered Iteration), it will generate as many indices as are needed to match its leader iterand.

Example (zipWithUnbounded.chpl).

The code

for i in zip(1..5, 3..) do
  write(i, "; ");

produces the output

(1, 3); (2, 4); (3, 5); (4, 6); (5, 7);

When an unbounded range of bool or enum values is used in a loop context, it is equivalent to a bounded range where the omitted low/high bound is taken to be the false/true for a bool range or the type’s initial/final value for an enum range.

Range Promotion of Scalar Functions

Range values may be passed to a scalar function argument whose type matches the range’s index type. This results in a promotion of the scalar function as described in Promotion.

Example (rangePromotion.chpl).

Given a function addOne(x: int) that accepts int values, the function addOne() can be called with the range 1..10 as its actual argument, which will result in the function being invoked for each value in the range in a data-parallel manner.

proc addOne(x: int) {
  return x + 1;
}
var A: [1..10] int;
A = addOne(1..10);

The last statement is equivalent to:

forall (a, i) in zip(A, 1..10) do
  a = addOne(i);

Range Operators

The following operators can be applied to range expressions and are described in this section: stride (by), alignment (align), count (#) and slicing (() or []). Chapel also defines a set of functions that operate on ranges. They are described in Predefined Routines on Ranges.

range-expression:
  expression
  strided-range-expression
  counted-range-expression
  aligned-range-expression
  sliced-range-expression

By Operator

The by operator selects a subsequence of the range’s represented sequence, optionally reversing its direction. The operator takes two arguments, a base range and an integral step. It produces a new range whose represented sequence contains each \(|\)step\(|\)-th element of the base range’s represented sequence. The operator reverses the direction of the represented sequence if step\(<\)0. If the resulting sequence is increasing, it starts at the base range’s aligned low bound, if it exists. If the resulting sequence is decreasing, it starts at the base range’s aligned high bound, if it exists. Otherwise, the base range’s alignment is used to determine which members of the represented sequence to retain. If the base range’s represented sequence is undefined, the resulting sequence is undefined, too.

The syntax of the by operator is:

strided-range-expression:
  range-expression 'by' step-expression

step-expression:
  expression

The step expression can be any integral or boolean type. Boolean values are interpreted as 0 or 1, respectively. It is an error for the step to be zero.

The type of the result of the by operator is the type of the base range, with the strides parameter updated according to the step. For example:

  • If the base range has strides=strideKind.one and the step is the literal -2, the result has strides=strideKind.negative.

  • If the base range has strides=strideKind.one and the step has the type uint, the result has strides=strideKind.positive.

  • If the step has the type int and is not a param, the result has strides=strideKind.any.

Formally, the result of the by operator is a range with the following primary properties:

  • The low and upper bounds are the same as those of the base range.

  • The stride is the product of the base range’s stride and the step, cast to the base range’s stride type before multiplying.

  • The alignment is:

    • the aligned low bound of the base range, if such exists and the stride is positive;

    • the aligned high bound of the base range, if such exists and the stride is negative;

    • the same as that of the base range, otherwise.

Example (rangeByOperator.chpl).

In the following declarations, range r1 represents the odd integers between 1 and 20. Range r2 strides r1 by two and represents every other odd integer between 1 and 20: 1, 5, 9, 13 and 17.

var r1 = 1..20 by 2;
var r2 = r1 by 2;

Rationale.

Why isn’t the high bound specified first if the stride is negative? The reason for this choice is that the by operator is binary, not ternary. Given a range R initialized to 1..3, we want R by -1 to contain the ordered sequence \(3,2,1\). But then R by -1 would be different from 3..1 by -1 even though it should be identical by substituting the value in R into the expression. We have also found that using a strict ordering of low and high bounds can be clearer when using ranges to slice arrays.

Align Operator

The align operator takes a base range and an alignment operand. It produces a copy of the base range with alignment set to the alignment argument, taken mod \({|stride|}\).

The syntax for the align operator is:

aligned-range-expression:
  range-expression 'align' expression

The type of the resulting range expression is the same as that of the base range. An implicit conversion from the type of the alignment operand to the index type of the base range must be allowed. The resulting range has the same low and high bounds and stride as the base range. The alignment equals the alignment operand mod \({|stride|}\) and therefore is not ambiguous.

Example (alignedStride.chpl).

var r1 = 0 .. 10 by 3 align 0;
for i in r1 do
  write(" ", i);                  // Produces " 0 3 6 9".
writeln();
var r2 = 0 .. 10 by 3 align 1;
for i in r2 do
  write(" ", i);                  // Produces " 1 4 7 10".
writeln();

When the stride is negative, the same indices are printed in reverse:

Example (alignedNegStride.chpl).

var r3 = 0 .. 10 by -3 align 0;
for i in r3 do
  write(" ", i);                  // Produces " 9 6 3 0".
writeln();
var r4 = 0 .. 10 by -3 align 1;
for i in r4 do
  write(" ", i);                  // Produces " 10 7 4 1".
writeln();

To set the alignment relative to the range’s first index, use the method offset.

Count Operator

The # operator takes a range and an integral count and creates a new range containing the specified number of indices. Specifically:

  • If the count is positive, \(count\) indices are taken starting from the first index of the range argument.

  • If the count is negative, \(-count\) indices are taken starting from the last index of the range argument.

  • If the count is zero, the result is an empty range.

counted-range-expression:
  range-expression # expression

The count expression can be any integral or boolean value, where boolean values are interpreted as 0 or 1, respectively.

In detail, the result of the # operator is calculated as follows. The type, stride, and alignment of the result are those of the range argument, except the result is always bounded.

Depending on the sign of the count and the stride, the high or low bound is unchanged and the other bound is adjusted to provide the specified number of indices. Specifically:

  • If the count and the stride are both positive or both negative, the indices are taken starting from the range’s low bound. In this case, the low bound is preserved and the high bound is set to \(low bound + count * stride - 1\).

  • If the count and the stride have opposite signs, the indices are taken starting from the range’s high bound. In this case, the high bound is preserved and the low bound is set to \(high bound + count * stride + 1\).

Rationale.

Following the principle of preserving as much information from the original range as possible, we must still choose the other bound so that exactly count indices lie within the range. Making the two bounds lie \(count * stride - 1\) apart will achieve this, regardless of the current alignment of the range.

This choice also has the nice symmetry that the alignment can be adjusted without knowing the bounds of the original range, and the same number of indices will be produced:

r # 4 align 0   // Contains four indices.
r # 4 align 1   // Contains four indices.
r # 4 align 2   // Contains four indices.
r # 4 align 3   // Contains four indices.

It is an error to apply the count operator with a positive count to a range that has no first index. It is also an error to apply the count operator with a negative count to a range that has no last index. It is an error to apply the count operator to an unaligned range. It is an error if the count is greater than the size of the range.

Example (rangeCountOperator.chpl).

The following declarations result in equivalent ranges.

var r1 = 1..10 by -2 # -3;
var r2 = ..6 by -2 # 3;
var r3 = -6..6 by -2 # 3;
var r4 = 1..#6 by -2;

Each of these ranges represents the ordered set of three indices: 6, 4, 2.

Warning

The count operator currently treats ranges over enum or bool types as bounded on both ends regardless of their bounds parameters. This behavior is unstable and might change in the future.

Arithmetic Operators

The following arithmetic operators are defined on ranges and integral types:

proc +(r: range, s: integral): range
proc +(s: integral, r: range): range
proc -(r: range, s: integral): range

The + and - operators apply the scalar via the operator to the range’s low and high bounds, producing a shifted version of the range. If the operand range is unbounded above or below, the missing bounds are ignored. The index type of the resulting range is the type of the value that would result from an addition between the scalar value and a value with the range’s index type. The bounds and strides parameters for the result range are the same as for the input range.

The stride of the resulting range is the same as the stride of the original. The alignment of the resulting range is shifted by the same amount as the high and low bounds. It is permissible to apply the shift operators to an unaligned range. In that case, the resulting range is also unaligned.

Example (rangeAdd.chpl).

The following code creates a range r which has an index type of int and represents the indices \({0, 1, 2, 3}\). It then uses the + operator to create a second range r2 representing the indices \({1, 2, 3, 4}\). Like r, the range r2 is bounded, its stride is 1 and cannot be changed, and its indices have the type int.

var r = 0..3;
var r2 = r + 1;    // 1..4

Warning

These operators are unstable. They may be removed or change behavior in the future.

Range Slicing

Ranges can be sliced using other ranges to create new sub-ranges. The resulting range represents the intersection between the two ranges’ represented sequences. The stride, alignment, and bounds of the resulting range are adjusted as needed to make this true. idxType of the result is determined by the first operand. The stride of the result is of the same sign as the stride of the first operand if the second operand’s stride is positive, and of the opposite sign otherwise.

Range slicing is specified by the syntax:

sliced-range-expression:
  range-expression ( range-expression )
  range-expression [ range-expression ]

Example (rangeSlicing.chpl).

In the following example, r represents the integers from 1 to 20 inclusive. Ranges r2 and r3 are defined using range slices and represent the indices from 3 to 20 and the odd integers between 1 and 20 respectively. Range r4 represents the odd integers between 1 and 20 that are also divisible by 3.

var r = 1..20;
var r2 = r[3..];
var r3 = r[1.. by 2];
var r4 = r3[0.. by 3];

It is an error for the first operand to be unaligned. If the second operand is unaligned, it is replaced with a range that is identical except it is given an alignment in such a way that that the intersection of the two ranges’ represented sequences is non-empty, if possible. How this substitute alignment is chosen when multiple possibilities are available is implementation-dependent.

If the resulting sequence cannot be expressed as a range with the original idxType, the slice expression evaluates to the empty range. This can happen, for example, when the first operand is an unbounded range with unsigned idxType and the second operand represents only negative numbers.

If the resulting sequence is empty, the bounds, stride, and alignment of the resulting range are implementation-dependent. If the resulting sequence is empty and both operands are unbounded in the same direction, it is an error.

Predefined Routines on Ranges

Range Type Queries

proc range.idxType type

Returns the type of the range’s indices (its idxType).

proc range.bounds param : boundKind

Returns which bounds the range explicitly represents (its bounds parameter).

proc range.strides param : strideKind

Returns what strides the range can have (its strides parameter).

proc range.strType type

Warning

range.strType is unstable and may be removed or renamed

Returns the type of the range’s stride.

proc range.stride

Returns the range’s stride.

proc range.alignment

Returns the range’s alignment.

proc range.isAligned()

Returns true if the range’s alignment is unambiguous, false otherwise.

proc range.hasLowBound() param

Returns true if this range’s low bound is not -\(\infty\), and false otherwise.

proc range.lowBound : idxType

Returns the range’s low bound. If the range does not have a low bound (e.g., ..10), the behavior is undefined. See also range.hasLowBound.

proc range.low : idxType

Returns the range’s aligned low bound. If this bound is undefined (e.g., ..10 by -2), the behavior is undefined.

Example:

var r = 1..10 by -2;
writeln(r.low);

produces the output

2
proc range.hasHighBound() param

Returns true if this range’s high bound is not \(\infty\), and false otherwise.

proc range.highBound : idxType

Returns the range’s high bound. If the range does not have a high bound (e.g., 1..), the behavior is undefined. See also range.hasHighBound.

proc range.high : idxType

Returns the range’s aligned high bound. If the aligned high bound is undefined (e.g., 1.. by 2), the behavior is undefined.

Example:

var r = 1..10 by 2;
writeln(r.high);

produces the output

9
proc range.isEmpty()

Returns true if the sequence represented by the range is empty and false otherwise. If the range is ambiguous, the behavior is undefined.

proc range.size : int

Returns the number of values represented by this range as an integer.

If the size exceeds max(int), this procedure will halt when bounds checks are on and have undefined behavior when they are not.

If the represented sequence is infinite or undefined, an error is generated.

proc range.sizeAs(type t: integral) : t

Returns the number of elements in this range as the specified integer type.

If the size exceeds the maximal value of that type, this procedure will halt when bounds checks are on and have undefined behavior when they are not.

If the represented sequence is infinite or undefined, an error is generated.

proc range.hasFirst()

Returns true if the range has a first index, false otherwise.

proc range.first

Returns the first value in the sequence the range represents. If the range has no first index, the behavior is undefined. See also range.hasFirst.

proc range.hasLast()

Returns true if the range has a last index, false otherwise.

proc range.last

Returns the last value in the sequence the range represents. If the range has no last index, the behavior is undefined. See also range.hasLast.

proc range.contains(ind: idxType)

Returns true if the range’s represented sequence contains ind, false otherwise. It is an error to invoke contains if the represented sequence is not defined.

proc range.contains(other: range(?))

Returns true if the range other is contained within this one, false otherwise.

proc range.tryCast(type t: range(?))

Casts a range to a new range type. Throws an IllegalArgumentError when the original bounds and/or stride do not fit in the new idxType or when the original stride is not legal for the new strides parameter.

proc range.indexOrder(ind: idxType)

Warning

range.indexOrder() is unstable and its behavior may change in the future

Returns an integer representing the zero-based ordinal value of ind within the range’s sequence of values if it is a member of the sequence. Otherwise, returns -1. It is an error to invoke indexOrder if the represented sequence is not defined or the range does not have a first index.

The following calls show the order of index 4 in each of the given ranges:

(0..10).indexOrder(4) == 4
(1..10).indexOrder(4) == 3
(3..5).indexOrder(4) == 1
(0..10 by 2).indexOrder(4) == 2
(3..5 by 2).indexOrder(4) == -1
proc range.orderToIndex(ord: integral) : idxType

Warning

range.orderToIndex() is unstable and its behavior may change in the future

Returns the zero-based ord-th element of this range’s represented sequence. It is an error to invoke orderToIndex if the range is not defined, or if ord is negative or greater than the range’s size. The orderToIndex procedure is the reverse of indexOrder.

Example:

0..10.orderToIndex(4) == 4
1..10.orderToIndex(3) == 4
3..5.orderToIndex(1)  == 4
0..10 by 2.orderToIndex(2) == 4
proc range.translate(offset: integral)

Warning

range.translate() is unstable and its behavior may change in the future

Returns a range with elements shifted from this range by offset. Formally, the range’s low bound, high bound, and alignment values will be shifted while the stride value will be preserved. If the range’s alignment is ambiguous, the behavior is undefined.

Example:

0..9.translate(1) == 1..10
0..9.translate(2) == 2..11
0..9.translate(-1) == -1..8
0..9.translate(-2) == -2..7
proc range.expand(offset: integral)

Warning

range.expand() is unstable and its behavior may change in the future

Returns a range expanded by offset elements from each end. If offset is negative, the range will be contracted. The stride and alignment of the original range are preserved.

Example:

0..9.expand(1)  == -1..10
0..9.expand(2)  == -2..11
0..9.expand(-1) == 1..8
0..9.expand(-2) == 2..7

Formally, for a range represented by the tuple \((l,h,s,a)\), the result is \((l-i,h+i,s,a)\). If the operand range is ambiguously aligned, then so is the resulting range.

proc range.interior(offset: integral)

Warning

range.interior() is unstable and its behavior may change in the future

Returns a range with offset elements from the interior portion of this range. If offset is positive, take elements from the high end, and if offset is negative, take elements from the low end.

Example:

0..9.interior(1)  == 9..9
0..9.interior(2)  == 8..9
0..9.interior(-1) == 0..0
0..9.interior(-2) == 0..1

Formally, given a range denoted by the tuple \((l,h,s,a)\),

  • if \(i < 0\), the result is \((l,l-(i-1),s,a)\),

  • if \(i > 0\), the result is \((h-(i-1),h,s,a)\), and

  • if \(i = 0\), the result is \((l,h,s,a)\).

This differs from the behavior of the count operator, in that interior() preserves the alignment, and it uses the low and high bounds rather than first and last to establish the bounds of the resulting range. If the operand range is ambiguously aligned, then so is the resulting range.

proc range.exterior(offset: integral)

Warning

range.exterior() is unstable and its behavior may change in the future

Returns a range with offset elements from the exterior portion of this range. If offset is positive, take elements from the high end, and if offset is negative, take elements from the low end.

Example:

0..9.exterior(1)  = 10..10
0..9.exterior(2)  = 10..11
0..9.exterior(-1) = -1..-1
0..9.exterior(-2) = -2..-1

Formally, given a range denoted by the tuple \((l,h,s,a)\),

  • if \(i < 0\), the result is \((l+i,l-1,s,a)\),

  • if \(i > 0\), the result is \((h+1,h+i,s,a)\), and

  • if \(i = 0\), the result is \((l,h,s,a)\).

If the operand range is ambiguously aligned, then so is the resulting range.

proc range.offset(in offset: integral)

Warning

range.offset() is unstable and its behavior may change in the future

Returns a range whose alignment is this range’s first index plus offset. If the range has no first index, a runtime error is generated.