NAME

Sidef::Types::Range::Range - Range class for representing sequences of values

DESCRIPTION

The Range class implements a lazy sequence of values between two endpoints. A range does not necessarily store every value up front; many methods pull values one at a time from an internal iterator (self.iter), which makes ranges memory-efficient and lets them represent infinite sequences (e.g. 1..Inf).

That said, several methods documented below (anything that says "delegates to the array form") first materialize the entire range into a plain Array (via "to_a") before doing their work. Calling one of those methods, or any lazy method that never stops iterating (like "map" or "grep" with no early exit), on a genuinely infinite or unbounded range will not terminate. Methods that can stop early -- such as "first_by", "any", "contains", or a bounded call to "first" -- are safe to use on infinite ranges.

A range can be created using the range operator .. or by calling the Range constructor. Ranges are commonly used in loops, array slicing, and generating sequences.

SYNOPSIS

# Create a range from 1 to 10
var r = (1..10)

# Iterate over range
(1..10).each { |n|
    say(n)
}

# Range with custom step
var evens = (0..20 `by` 2)

# Infinite ranges
var infinite = (1..Inf)

# Map over a range
var squares = (1..5).map { |n| n**2 }

# Filter range values
var odds = (1..20).grep { .is_odd }

# Shifting a range's bounds by a scalar
var r1 = (1..5)
say(r1 + 10)   #=> a new range from 11 to 15

INHERITS

Inherits methods from:

* Sidef::Object::Object

CONSTRUCTION

new

Range.new(from, to, step)
Range(from, to, step)

Creates a new Range. This actually delegates directly to calling .range(to, step) on the given from value, so the real construction logic (including how a missing step is inferred from the direction of from and to) lives in from's own range method, not in this class.

var r1 = Range.new(1, 10)
var r2 = Range.new(0, 20, 2)
var r3 = Range(5, 1, -1)

Aliases: call

call

Range(from, to, step)

Allows Range to be invoked directly as a function, equivalent to "new".

Aliases: new

BOUNDS AND STEP

from

self.from
self.from(new_from)

With no argument, returns the range's start bound. With an argument, returns a new range with the start bound replaced by new_from, keeping the same end bound and step.

say((1..10).from)         #=> 1
say((1..10).from(5)...)   #=> [5, 6, 7, 8, 9, 10]

to

self.to
self.to(new_to)

With no argument, returns the range's end bound. With an argument, returns a new range with the end bound replaced by new_to, keeping the same start bound and step.

say((1..10).to)        #=> 10
say((1..5).to(10)...)  #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

by

self.by
self.by(step)

With no argument, returns the range's current step (same as "step"). With an argument, returns a new range using step as its step size, keeping the same bounds. If the range is currently descending (its existing step is negative), step is automatically negated first, so the range keeps counting down.

say((1..10 `by` 2)...)    #=> [1, 3, 5, 7, 9]
say((0..20 `by` 5)...)    #=> [0, 5, 10, 15, 20]
say((10..1 `by` 2)...)    #=> [10, 8, 6, 4, 2]  # 2 is negated internally to -2

step

self.step

Returns the step size of the range, as set at construction time (or inferred as 1 / -1 depending on direction).

say((1..10).step)          #=> 1
say((0..20 `by` 5).step)   #=> 5
say((10..1).step)          #=> -1

min

self.min
self.min(block)

With no block, returns the numerically smaller of the range's two declared endpoints (not necessarily the last value actually produced by iteration, if step does not evenly divide the interval). With a block, delegates entirely to "min_by".

say((1..10).min)   #=> 1
say((10..1).min)   #=> 1

min_by

self.min_by(block)

Iterates the range lazily and returns the element for which block produces the smallest value (comparing results with the cmp operator). If block is omitted, elements are compared directly. On ties, the first element encountered is kept.

say((-5..5).min_by { |n| n.abs })   #=> 0

max

self.max
self.max(block)

With no block, returns the numerically larger of the range's two declared endpoints (subject to the same caveat as "min" about non-evenly-dividing steps). With a block, delegates entirely to "max_by".

say((1..10).max)   #=> 10
say((10..1).max)   #=> 10

max_by

self.max_by(block)

Iterates the range lazily and returns the element for which block produces the largest value. If block is omitted, elements are compared directly. On ties, the first element encountered is kept.

say((-5..5).max_by { |n| n.abs })   #=> 5

bounds

self.bounds

Returns a two-element list (min, max) -- always in ascending numeric order, regardless of whether the range counts up or down.

say((1..10).bounds)          #=> (1, 10)
say((10..1).bounds)          #=> (1, 10)
say((0..100 `by` 5).bounds)  #=> (0, 100)

len

self.len

Returns the number of elements the range would generate, computed directly from its bounds and step (without iterating), as int((to - from + step) / step). Returns 0 if the computed length would be negative.

say((1..10).len)           #=> 10
say((0..20 `by` 5).len)    #=> 5
say((10..1).len)           #=> 10

Aliases: length

reverse

self.reverse

Returns a new range with from and to swapped and the step negated, effectively reversing the direction of iteration.

say((1..10).reverse...)   #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
say((5..1).reverse...)    #=> [1, 2, 3, 4, 5]

Aliases: flip

neg

self.neg

Returns a new range with only the from endpoint negated; to and step are left unchanged. This does not negate every element of the sequence -- only the starting bound.

say((1..5).neg...)    #=> [-1, 0, 1, 2, 3, 4, 5]  # from: 1 -> -1; to (5) and step (1) unchanged
say((-3..3).neg...)   #=> [3]                      # from: -3 -> 3; to (3) and step (1) unchanged, so from == to

MEMBERSHIP AND COUNTING

contains

self.contains(value)

Checks whether value falls within the range's bounds and lies exactly on its step lattice (i.e., could actually be produced by iterating the range), taking direction into account.

say((1..10).contains(5))        #=> true
say((1..10).contains(15))       #=> false
say((1..10 `by` 2).contains(3)) #=> false
say((1..10 `by` 2).contains(5)) #=> true

Aliases: contain, include, includes

count

self.count(value)
self.count(block)

If given a plain value, returns 1 if the range "contains" it, or 0 otherwise (ranges never contain duplicate values, so this is a membership test rather than a general tally). If given a block, delegates to "count_by".

say((1..10).count(5))               #=> 1
say((1..10).count(15))              #=> 0
say((1..10).count { |n| n.is_even }) #=> 5

count_by

self.count_by(block)

Iterates the range lazily and returns the number of elements for which block returns a truthy value.

say((1..10).count_by { |n| n % 3 == 0 })    #=> 3
say((1..20).count_by { |n| n.is_prime })    #=> 8

ITERATION

each

self.each(block)

Iterates the range lazily, calling block once per element for side effects, and returns self.

(1..5).each { |n|
    say(n)
}
# Prints: 1, 2, 3, 4, 5

Aliases: for, foreach

while

self.while(block)

Iterates the range lazily, calling block on each successive element, and stops as soon as block returns a falsy value (the element that caused the stop is consumed but not itself processed further). Unlike "grep", this does not collect any values -- it returns self.

(1..100).while { |n|
    say(n)
    n < 5
}
# Prints: 1, 2, 3, 4, 5, then stops (6 fails the condition and iteration ends)

each_cons

self.each_cons(n, block)

Iterates the range lazily over overlapping, consecutive windows of n elements, calling block with each window's elements as separate arguments (not as a single array). Returns self. If n is zero or negative, this is a no-op.

(1..10).each_cons(3, { |*group|
    say(group)
})
# Prints: [1, 2, 3], [2, 3, 4], [3, 4, 5], ...

each_slice

self.each_slice(n, block)

Iterates the range lazily over non-overlapping slices of n elements, calling block with each slice's elements as separate arguments. The final, possibly shorter, slice is still passed to block. Returns self. If n is zero or negative, this is a no-op.

(1..10).each_slice(3, { |*slice|
    say(slice)
})
# Prints: [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]

SEARCHING

first

self.first
self.first(n)
self.first(block)

With no argument, returns the single first element of the range (or nil if empty). With a number n, returns an Array of the first n elements. With a block, delegates entirely to "first_by".

say((1..100).first)      #=> 1
say((1..100).first(5))   #=> [1, 2, 3, 4, 5]
say((10..1).first(3))    #=> [10, 9, 8]

Aliases: head

first_by

self.first_by(block)

Iterates the range lazily and returns the first element for which block returns a truthy value, or nil if none does. Safe to use on infinite ranges, as long as a match exists.

say((1..100).first_by { |n| n > 50 && n.is_prime })  #=> 53
say((1..10).first_by { |n| n > 100 })  #=> nil

last

self.last
self.last(n)
self.last(block)

With no argument, returns the single last element of the range. With a number n, returns an Array of the last n elements, in their original order. With a block, returns the last element for which the block is truthy (equivalent to "last_by"). Implemented by reversing the range and searching from the other end, so it requires the range to be bounded.

say((1..10).last)      #=> 10
say((1..10).last(3))   #=> [8, 9, 10]

Aliases: tail

last_by

self.last_by(block)

Returns the last element for which block returns a truthy value, or nil if none does. Implemented as self.reverse.first_by(block), so it requires the range to be bounded.

say((1..100).last_by { |n| n < 50 && n.is_prime })  #=> 47
say((1..10).last_by { |n| n > 100 })  #=> nil

PREDICATES

any

self.any(block)

Returns true as soon as block returns a truthy value for some element; returns false if the range is exhausted with no match. If block is omitted, elements are tested directly for truthiness.

say((1..10).any { |n| n > 5 })     #=> true
say((1..10).any { |n| n > 20 })    #=> false

all

self.all(block)

Returns false as soon as block returns a falsy value for some element; returns true if every element passes (including if the range is empty). If block is omitted, elements are tested directly for truthiness.

say((1..10).all { |n| n > 0 })     #=> true
say((1..10).all { |n| n.is_even }) #=> false

none

self.none(block)

Returns false as soon as block returns a truthy value for some element; returns true if no element matches. If block is omitted, elements are tested directly for truthiness.

say((1..10).none { |n| n > 20 })     #=> true
say((1..10).none { |n| n > 5 })      #=> false

TRANSFORMATION

map

self.map(block)

Iterates the range lazily, calling block once per element, and collects the results into a new Array. If block is omitted, the identity function is used (equivalent to "to_a").

say((1..5).map { |n| n**2 })      #=> [1, 4, 9, 16, 25]
say((1..3).map { |n| n * 10 })    #=> [10, 20, 30]

grep

self.grep(block)

Iterates the range lazily and collects into a new Array only the elements for which block returns a truthy value. If block is omitted, elements are kept based on their own truthiness.

say((1..20).grep { |n| n.is_prime })  #=> [2, 3, 5, 7, 11, 13, 17, 19]
say((1..10).grep { |n| n > 5 })       #=> [6, 7, 8, 9, 10]

Aliases: select

map_cons

self.map_cons(n, block)

Materializes the range into an Array (via "to_a") and delegates to that array's own map_cons method, which maps block over overlapping, consecutive groups of n elements and collects the results.

say((1..10).map_cons(3, { |*group| group.sum }))  #=> [6, 9, 12, 15, 18, 21, 24, 27]

Aliases: cons

map_slice

self.map_slice(n, block)

Materializes the range into an Array (via "to_a") and delegates to that array's own map_slice method, which maps block over non-overlapping slices of n elements and collects the results.

say((1..10).map_slice(3, { |*slice| slice.sum }))
#=> [6, 15, 24, 10]

Aliases: slices

sort

self.sort(block)

Materializes the range into an Array (via "to_a") and delegates to that array's own sort method. With no block, sorts in natural order; with a block, uses it as the comparator.

say((1..10).sort { |a, b| b <=> a })  #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

sort_by

self.sort_by(block)

Materializes the range into an Array (via "to_a") and delegates to that array's own sort_by method, sorting by the value block returns for each element.

say((-5..5).sort_by { |n| n.abs })    #=> [0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5]

accumulate

self.accumulate(block)

Materializes the range into an Array (via "to_a") and delegates to that array's own accumulate method, returning the running (cumulative) result of applying block pairwise across the elements.

say((1..5).accumulate { |a, b| a + b })  #=> [1, 3, 6, 10, 15]
say((1..4).accumulate { |a, b| a * b })  #=> [1, 2, 6, 24]

accumulate_by

self.accumulate_by(block)

Materializes the range into an Array (via "to_a") and delegates to that array's own accumulate_by method: applies block to each element first, then accumulates the results.

say((1..5).accumulate_by { |n| n**2 })  #=> [1, 5, 14, 30, 55]

REDUCTION

reduce

self.reduce(block)
self.reduce(block, initial)
self.reduce(op_name)
self.reduce(op_name, initial)

Reduces the range to a single value. If given a block, iterates lazily, using initial (if given) or the first element as the starting accumulator, then repeatedly calls block(accumulator, next_element). If given a string naming an operator (such as '+'), delegates to "reduce_operator" instead.

say((1..5).reduce { |a, b| a + b })      #=> 15
say((1..4).reduce { |a, b| a * b })      #=> 24
say((1..10).reduce('+'))                 #=> 55
say((1..5).reduce('+', 100))             #=> 115

reduce_operator

self.reduce_operator(op_name, initial=nil)

Reduces the range to a single value by repeatedly calling the named operator method (e.g. '+', '*') on an accumulator, starting from initial (or the first element, if omitted).

say((1..10).reduce_operator('+'))     #=> 55
say((1..4).reduce_operator('*'))      #=> 24

map_operator

self.map_operator(op_name, *args)

Materializes the range into an Array (via "to_a") and delegates to that array's own map_operator method, applying the named operator between each element and args.

say((1..5).map_operator('+', 10))  #=> [11, 12, 13, 14, 15]
say((1..5).map_operator('**', 2))  #=> [1, 4, 9, 16, 25]

pam_operator

self.pam_operator(op_name, *args)

Materializes the range into an Array (via "to_a") and delegates to that array's own pam_operator method. See the Array documentation for exact semantics.

say((1..5).pam_operator('+', 10))

unroll_operator

self.unroll_operator(op_name, *args)

Materializes the range into an Array (via "to_a") and delegates to that array's own unroll_operator method. See the Array documentation for exact semantics.

say((1..5).unroll_operator('+', 10))

cross_operator

self.cross_operator(op_name, *args)

Materializes the range into an Array (via "to_a") and delegates to that array's own cross_operator method, combining elements from self and args pairwise (cross-product style) using the named operator.

say((1..3).cross_operator('+', 10..12))
#=> [[11, 12, 13], [12, 13, 14], [13, 14, 15]]

zip_operator

self.zip_operator(op_name, *args)

Materializes the range into an Array (via "to_a") and delegates to that array's own zip_operator method, combining corresponding elements from self and args using the named operator.

say((1..3).zip_operator('+', 10..12))  #=> [11, 13, 15]

RANDOMIZATION

rand

self.rand
self.rand(n)

With no argument, returns a single random element from the range. With n, returns an Array of n random elements, drawn with replacement (duplicates possible). Computed directly from the bounds/step, without materializing the range.

say((1..10).rand)      #=> a random number between 1 and 10
say((1..10).rand(5))   #=> 5 random numbers, possibly with duplicates

Aliases: sample

pick

self.pick
self.pick(n)

With no argument, returns a single random element from the range. With n, returns an Array of n distinct random elements (without replacement -- no duplicates), computed directly from the bounds/step.

say((1..10).pick)      #=> a random number between 1 and 10
say((1..10).pick(3))   #=> 3 distinct random numbers, e.g. [7, 2, 9]

shuffle

self.shuffle

Materializes the range into a list and returns a new Array with its elements in random order.

say((1..10).shuffle)   #=> a random permutation, e.g. [3, 7, 1, 9, 2, 5, 10, 4, 8, 6]

CONVERSION

to_array

self.to_array

Materializes the range by iterating it fully and returns the result as a new Array object.

say((1..5).to_array)   #=> [1, 2, 3, 4, 5]

Aliases: to_a

to_list

self.to_list

Materializes the range by iterating it fully and returns its values as a plain list (not wrapped in an Array object, unlike "to_a").

var arr = (1..5)...
say(arr)  #=> [1, 2, 3, 4, 5]

Aliases: operator ...

...

a...

Operator form of "to_list".

Aliases: to_list

to_vector

self.to_vector

Materializes the range (via "to_a") and converts it to a Vector object.

var vec = (1..5).to_vector

Aliases: to_v, to_vec

join

self.join(sep)

Materializes the range (via "to_list") and returns a String with its elements joined by sep.

say((1..5).join(', '))    #=> "1, 2, 3, 4, 5"
say((1..3).join(' + '))   #=> "1 + 2 + 3"

kv

self.kv

Materializes the range into an Array (via "to_a") and delegates to that array's own kv method, returning an array of [index, value] pairs.

say((10..13).kv)  #=> [[0, 10], [1, 11], [2, 12], [3, 13]]

Aliases: pairs, zip_indices

ARITHMETIC

The operators below create a new range by applying the given operation to both of self's endpoints (the from and to values), leaving the step unchanged. arg is typically a plain number; since the operation is delegated directly to the endpoint values' own arithmetic methods, the result of using something other than a plain number as arg depends on how that value's own operator is implemented (outside the scope of this class).

add

self.add(arg)
self + arg

Returns a new range with arg added to both from and to.

say((1..5) + 10)     #=> a range from 11 to 15

Aliases: operator +

+

self + arg

Operator form of "add".

Aliases: add

sub

self.sub(arg)
self - arg

Returns a new range with arg subtracted from both from and to.

say((10..15) - 5)    #=> a range from 5 to 10

Aliases: operator -

-

self - arg

Operator form of "sub".

Aliases: sub

mul

self.mul(arg)
self * arg

Returns a new range with both from and to multiplied by arg.

say((1..5) * 2)      #=> a range from 2 to 10, step 1

Aliases: operator *

*

self * arg

Operator form of "mul".

Aliases: mul

div

self.div(arg)
self / arg

Returns a new range with both from and to divided by arg.

say((10..20 `by` 5) / 2)  #=> a range from 5 to 10, step 5

Aliases: operator /, operator ÷

/

self / arg

Operator form of "div".

Aliases: div

÷

self ÷ arg

Operator form of "div".

Aliases: div

COMPARISON

eq

self.eq(other)
self == other

Returns true if other is also a Range (of the exact same class) with the same from, to, and step; otherwise false.

say((1..10) == (1..10))  #=> true
say((1..10) == (1..20))  #=> false
say((1..10 `by` 2) == (1..10)) #=> false

Aliases: operator ==

==

self == other

Operator form of "eq".

Aliases: eq

ne

self.ne(other)
self != other

Returns the logical negation of "eq".

say((1..10) != (1..20))  #=> true
say((1..10) != (1..10))  #=> false

Aliases: operator !=, operator

!=

self != other

Operator form of "ne".

Aliases: ne

self ≠ other

Operator form of "ne".

Aliases: ne

SEE ALSO

Sidef::Types::Array::Array, Sidef::Types::Number::Number