=encoding utf8

=head1 NAME

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

=head1 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 (C<self.iter>), which makes ranges memory-efficient and lets them represent infinite sequences (e.g. C<1..Inf>).

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

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

=head1 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

=head1 INHERITS

Inherits methods from:

       * Sidef::Object::Object

=head1 CONSTRUCTION

=head2 new

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

Creates a new C<Range>. This actually delegates directly to calling C<.range(to, step)> on the given C<from> value, so the real construction logic (including how a missing C<step> is inferred from the direction of C<from> and C<to>) lives in C<from>'s own C<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: I<call>

=cut

=head2 call

    Range(from, to, step)

Allows C<Range> to be invoked directly as a function, equivalent to L</new>.

Aliases: I<new>

=cut

=head1 BOUNDS AND STEP

=head2 from

    self.from
    self.from(new_from)

With no argument, returns the range's start bound. With an argument, returns a I<new> range with the start bound replaced by C<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]

=cut

=head2 to

    self.to
    self.to(new_to)

With no argument, returns the range's end bound. With an argument, returns a I<new> range with the end bound replaced by C<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]

=cut

=head2 by

    self.by
    self.by(step)

With no argument, returns the range's current step (same as L</step>). With an argument, returns a I<new> range using C<step> as its step size, keeping the same bounds. If the range is currently descending (its existing step is negative), C<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

=cut

=head2 step

    self.step

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

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

=cut

=head2 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 C<step> does not evenly divide the interval). With a block, delegates entirely to L</min_by>.

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

=cut

=head2 min_by

    self.min_by(block)

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

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

=cut

=head2 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 L</min> about non-evenly-dividing steps). With a block, delegates entirely to L</max_by>.

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

=cut

=head2 max_by

    self.max_by(block)

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

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

=cut

=head2 bounds

    self.bounds

Returns a two-element list C<(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)

=cut

=head2 len

    self.len

Returns the number of elements the range would generate, computed directly from its bounds and step (without iterating), as C<int((to - from + step) / step)>. Returns C<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: I<length>

=cut

=head2 reverse

    self.reverse

Returns a new range with C<from> and C<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: I<flip>

=cut

=head2 neg

    self.neg

Returns a new range with only the C<from> endpoint negated; C<to> and C<step> are left B<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

=cut

=head1 MEMBERSHIP AND COUNTING

=head2 contains

    self.contains(value)

Checks whether C<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: I<contain>, I<include>, I<includes>

=cut

=head2 count

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

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

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

=cut

=head2 count_by

    self.count_by(block)

Iterates the range lazily and returns the number of elements for which C<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

=cut

=head1 ITERATION

=head2 each

    self.each(block)

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

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

Aliases: I<for>, I<foreach>

=cut

=head2 while

    self.while(block)

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

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

=cut

=head2 each_cons

    self.each_cons(n, block)

Iterates the range lazily over overlapping, consecutive windows of C<n> elements, calling C<block> with each window's elements as separate arguments (not as a single array). Returns C<self>. If C<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], ...

=cut

=head2 each_slice

    self.each_slice(n, block)

Iterates the range lazily over non-overlapping slices of C<n> elements, calling C<block> with each slice's elements as separate arguments. The final, possibly shorter, slice is still passed to C<block>. Returns C<self>. If C<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]

=cut

=head1 SEARCHING

=head2 first

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

With no argument, returns the single first element of the range (or C<nil> if empty). With a number C<n>, returns an C<Array> of the first C<n> elements. With a block, delegates entirely to L</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: I<head>

=cut

=head2 first_by

    self.first_by(block)

Iterates the range lazily and returns the first element for which C<block> returns a truthy value, or C<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

=cut

=head2 last

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

With no argument, returns the single last element of the range. With a number C<n>, returns an C<Array> of the last C<n> elements, in their original order. With a block, returns the last element for which the block is truthy (equivalent to L</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: I<tail>

=cut

=head2 last_by

    self.last_by(block)

Returns the last element for which C<block> returns a truthy value, or C<nil> if none does. Implemented as C<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

=cut

=head1 PREDICATES

=head2 any

    self.any(block)

Returns C<true> as soon as C<block> returns a truthy value for some element; returns C<false> if the range is exhausted with no match. If C<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

=cut

=head2 all

    self.all(block)

Returns C<false> as soon as C<block> returns a falsy value for some element; returns C<true> if every element passes (including if the range is empty). If C<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

=cut

=head2 none

    self.none(block)

Returns C<false> as soon as C<block> returns a truthy value for some element; returns C<true> if no element matches. If C<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

=cut

=head1 TRANSFORMATION

=head2 map

    self.map(block)

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

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

=cut

=head2 grep

    self.grep(block)

Iterates the range lazily and collects into a new C<Array> only the elements for which C<block> returns a truthy value. If C<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: I<select>

=cut

=head2 map_cons

    self.map_cons(n, block)

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

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

Aliases: I<cons>

=cut

=head2 map_slice

    self.map_slice(n, block)

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

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

Aliases: I<slices>

=cut

=head2 sort

    self.sort(block)

Materializes the range into an C<Array> (via L</to_a>) and delegates to that array's own C<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]

=cut

=head2 sort_by

    self.sort_by(block)

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

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

=cut

=head2 accumulate

    self.accumulate(block)

Materializes the range into an C<Array> (via L</to_a>) and delegates to that array's own C<accumulate> method, returning the running (cumulative) result of applying C<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]

=cut

=head2 accumulate_by

    self.accumulate_by(block)

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

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

=cut

=head1 REDUCTION

=head2 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 C<initial> (if given) or the first element as the starting accumulator, then repeatedly calls C<block(accumulator, next_element)>. If given a string naming an operator (such as C<'+'>), delegates to L</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

=cut

=head2 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. C<'+'>, C<'*'>) on an accumulator, starting from C<initial> (or the first element, if omitted).

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

=cut

=head2 map_operator

    self.map_operator(op_name, *args)

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

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

=cut

=head2 pam_operator

    self.pam_operator(op_name, *args)

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

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

=cut

=head2 unroll_operator

    self.unroll_operator(op_name, *args)

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

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

=cut

=head2 cross_operator

    self.cross_operator(op_name, *args)

Materializes the range into an C<Array> (via L</to_a>) and delegates to that array's own C<cross_operator> method, combining elements from C<self> and C<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]]

=cut

=head2 zip_operator

    self.zip_operator(op_name, *args)

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

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

=cut

=head1 RANDOMIZATION

=head2 rand

    self.rand
    self.rand(n)

With no argument, returns a single random element from the range. With C<n>, returns an C<Array> of C<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: I<sample>

=cut

=head2 pick

    self.pick
    self.pick(n)

With no argument, returns a single random element from the range. With C<n>, returns an C<Array> of C<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]

=cut

=head2 shuffle

    self.shuffle

Materializes the range into a list and returns a new C<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]

=cut

=head1 CONVERSION

=head2 to_array

    self.to_array

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

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

Aliases: I<to_a>

=cut

=head2 to_list

    self.to_list

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

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

Aliases: operator C<...>

=cut

=head2 ...

    a...

Operator form of L</to_list>.

Aliases: I<to_list>

=cut

=head2 to_vector

    self.to_vector

Materializes the range (via L</to_a>) and converts it to a C<Vector> object.

    var vec = (1..5).to_vector

Aliases: I<to_v>, I<to_vec>

=cut

=head2 join

    self.join(sep)

Materializes the range (via L</to_list>) and returns a C<String> with its elements joined by C<sep>.

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

=cut

=head2 kv

    self.kv

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

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

Aliases: I<pairs>, I<zip_indices>

=cut

=head1 ARITHMETIC

The operators below create a I<new> range by applying the given operation to both of C<self>'s endpoints (the C<from> and C<to> values), leaving the step unchanged. C<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 C<arg> depends on how that value's own operator is implemented (outside the scope of this class).

=head2 add

    self.add(arg)
    self + arg

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

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

Aliases: operator C<+>

=cut

=head2 +

    self + arg

Operator form of L</add>.

Aliases: I<add>

=cut

=head2 sub

    self.sub(arg)
    self - arg

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

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

Aliases: operator C<->

=cut

=head2 -

    self - arg

Operator form of L</sub>.

Aliases: I<sub>

=cut

=head2 mul

    self.mul(arg)
    self * arg

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

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

Aliases: operator C<*>

=cut

=head2 *

    self * arg

Operator form of L</mul>.

Aliases: I<mul>

=cut

=head2 div

    self.div(arg)
    self / arg

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

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

Aliases: operator C<E<sol>>, operator C<÷>

=cut

=head2 /

    self / arg

Operator form of L</div>.

Aliases: I<div>

=cut

=head2 ÷

    self ÷ arg

Operator form of L</div>.

Aliases: I<div>

=cut

=head1 COMPARISON

=head2 eq

    self.eq(other)
    self == other

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

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

Aliases: operator C<==>

=cut

=head2 ==

    self == other

Operator form of L</eq>.

Aliases: I<eq>

=cut

=head2 ne

    self.ne(other)
    self != other

Returns the logical negation of L</eq>.

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

Aliases: operator C<!=>, operator C<≠>

=cut

=head2 !=

    self != other

Operator form of L</ne>.

Aliases: I<ne>

=cut

=head2 ≠

    self ≠ other

Operator form of L</ne>.

Aliases: I<ne>

=cut

=head1 SEE ALSO

L<Sidef::Types::Array::Array>, L<Sidef::Types::Number::Number>

=cut
