=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. Ranges are memory-efficient as they don't store all values but generate them on demand. They support arithmetic operations, iteration, and functional programming methods.

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 }
    
    # Range arithmetic
    var r1 = (1..5)
    var r2 = (10..15)
    say (r1 + r2)  # Add ranges

=head1 METHODS

=head2 *

    a * b

Multiplies the range by a scalar value or another range. When multiplying by a scalar, each element in the range is multiplied. When multiplying by another range, performs element-wise multiplication.

    say ((1..5) * 2)      #=> Range(2, 4, 6, 8, 10)
    say ((1..3) * (2..4)) #=> Range(2, 6, 12)

Aliases: I<mul>

=cut

=head2 +

    a + b

Adds a scalar value or another range to the range. When adding a scalar, adds it to each element. When adding another range, performs element-wise addition.

    say ((1..5) + 10)     #=> Range(11, 12, 13, 14, 15)
    say ((1..3) + (5..7)) #=> Range(6, 8, 10)

Aliases: I<add>

=cut

=head2 -

    a - b

Subtracts a scalar value or another range from the range. When subtracting a scalar, subtracts it from each element. When subtracting another range, performs element-wise subtraction.

    say ((10..15) - 5)    #=> Range(5, 6, 7, 8, 9, 10)
    say ((10..12) - (1..3)) #=> Range(9, 10, 11)

Aliases: I<sub>

=cut

=head2 ...

    a ... b

Converts the range to a list (array) containing all values in the range. This forces evaluation of the lazy range.

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

Aliases: I<to_list>

=cut

=head2 /

    a / b

Divides the range by a scalar value or another range. When dividing by a scalar, divides each element. When dividing by another range, performs element-wise division.

    say ((10..20 `by` 5) / 2)  #=> Range(5, 7.5, 10)
    say ((10..30 `by` 10) / (2..4)) #=> Range(5, 10, 15)

Aliases: I<÷>, I<div>

=cut

=head2 ==

    a == b

Checks if two ranges are equal. Ranges are equal if they have the same bounds, step, and direction.

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

Aliases: I<eq>

=cut

=head2 ≠

    a ≠ b

Checks if two ranges are not equal. Returns true if the ranges differ in bounds, step, or direction.

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

Aliases: I<!=>, I<ne>

=cut

=head2 accumulate

    self.accumulate(block)

Returns an array of accumulated values by applying the given block (or operator) cumulatively. Similar to a running sum or cumulative operation.

    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)

Returns an array of accumulated values by applying the given block to each element first, then accumulating.

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

=cut

=head2 all

    self.all(block)

Returns true if all elements in the range satisfy the condition in the block. Returns false otherwise.

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

=cut

=head2 any

    self.any(block)

Returns true if at least one element in the range satisfies the condition in the block. Returns false if none do.

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

=cut

=head2 bounds

    self.bounds

Returns an array containing the start and end bounds of the range.

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

=cut

=head2 by

    self.by(step)

Creates a new range with the specified step size. This allows you to skip values in the sequence.

    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]

=cut

=head2 cons

    self.cons(n, block)

Maps over consecutive groups of n elements from the range. Returns an array of results from applying the block to each consecutive group.

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

Aliases: I<map_cons>

=cut

=head2 contain

    self.contain(value)

Checks if the range contains the specified value. Takes into account the range's bounds, step, and direction.

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

Aliases: I<include>, I<contains>, I<includes>

=cut

=head2 count

    self.count(arg)

Counts elements in the range that match the given value or satisfy the given block condition.

    say (1..10).count(5)            #=> 1
    say (1..10).count { |n| n.is_even }  #=> 5
    say (1..20).count { |n| n.is_prime } #=> 8

=cut

=head2 count_by

    self.count_by(block)

Returns a hash with counts of elements grouped by the result of applying the block to each element.

    say (1..10).count_by { |n| n % 3 }
    #=> Hash(0 => 3, 1 => 4, 2 => 3)
    
    say (1..10).count_by { |n| n.is_even ? "even" : "odd" }
    #=> Hash("even" => 5, "odd" => 5)

=cut

=head2 cross_operator

    self.cross_operator(*args)

Performs a cross product operation with other ranges or arrays, applying an operator to combinations.

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

=cut

=head2 each_cons

    self.each_cons(n, block)

Iterates over consecutive groups of n elements, passing each group to the block. Similar to cons but doesn't collect results.

    (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 over the range in slices of n elements, passing each slice to the block.

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

=cut

=head2 first_by

    self.first_by(block)

Returns the first element for which the block returns true, or nil if no element satisfies the condition.

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

=cut

=head2 flip

    self.flip

Returns a new range with reversed bounds (flips the direction of iteration).

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

Aliases: I<reverse>

=cut

=head2 for

    self.for(block)

Iterates over each element in the range, passing it to the block. This is the standard iteration method.

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

Aliases: I<each>, I<foreach>

=cut

=head2 from

    self.from(from)

Creates a new range starting from the specified value, keeping the original end bound.

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

=cut

=head2 grep

    self.grep(block)

Filters the range, returning an array of elements that satisfy the condition in the block.

    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 head

    self.head(num)

Returns an array containing the first num elements of the range.

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

Aliases: I<first>

=cut

=head2 join

    self.join(sep)

Converts the range to a string with elements joined by the specified separator.

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

=cut

=head2 kv

    self.kv

Returns an array of [index, value] pairs for each element in the range.

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

Aliases: I<pairs>, I<zip_indices>

=cut

=head2 last

    self.last(num)

Returns an array containing the last num elements of the range.

    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 the block returns true, or nil if no element satisfies the condition.

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

=cut

=head2 len

    self.len

Returns the number of elements in the range.

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

Aliases: I<length>

=cut

=head2 map

    self.map(block)

Transforms each element in the range using the given block, returning an array of results.

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

=cut

=head2 map_operator

    self.map_operator(op, *args)

Maps an operator over the range with additional arguments.

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

=cut

=head2 max

    self.max(block)

Returns the maximum element in the range. With a block, compares elements using the block's return value.

    say (1..10).max              #=> 10
    say (1..10).max { |a, b| b <=> a }  #=> 1 (reverse order)

=cut

=head2 max_by

    self.max_by(block)

Returns the element that produces the maximum value when passed through the block.

    say (-5..5).max_by { |n| n.abs }      #=> -5 or 5
    say (1..10).max_by { |n| n % 3 }      #=> 2 (or 5 or 8)

=cut

=head2 min

    self.min(block)

Returns the minimum element in the range. With a block, compares elements using the block's return value.

    say (1..10).min              #=> 1
    say (1..10).min { |a, b| b <=> a }  #=> 10 (reverse order)

=cut

=head2 min_by

    self.min_by(block)

Returns the element that produces the minimum value when passed through the block.

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

=cut

=head2 neg

    self.neg

Returns a new range with all elements negated.

    say (1..5).neg...    #=> [-1, -2, -3, -4, -5]
    say (-3..3).neg...   #=> [3, 2, 1, 0, -1, -2, -3]

=cut

=head2 new

    Range.new(from, to, step)

Creates a new range object with the specified start, end, and optional step values.

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

Aliases: I<call>

=cut

=head2 none

    self.none(block)

Returns true if no elements in the range satisfy the condition in the block.

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

=cut

=head2 pam_operator

    self.pam_operator(op, *args)

Applies the pam operator (parallel map) over the range elements.

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

=cut

=head2 pick

    self.pick(n)

Returns n random elements from the range without replacement. If n is not specified, returns one random element.

    say (1..10).pick      #=> (random number from 1 to 10)
    say (1..10).pick(3)   #=> [random 3 numbers, e.g., [7, 2, 9]]

=cut

=head2 rand

    self.rand(n)

Returns n random elements from the range with replacement. Each call can return duplicate values.

    say (1..10).rand      #=> (random number from 1 to 10)
    say (1..10).rand(5)   #=> [5 random numbers, may contain duplicates]

Aliases: I<sample>

=cut

=head2 reduce

    self.reduce(op, initial)

Reduces the range to a single value by applying the given operator or block cumulatively. The initial value is optional.

    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, initial)

Reduces the range using a named operator. Similar to reduce but takes an operator name as a string.

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

=cut

=head2 shuffle

    self.shuffle

Returns an array with the range elements in random order.

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

=cut

=head2 slices

    self.slices(n, block)

Maps over slices of n elements, applying the block to each slice and returning an array of results.

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

Aliases: I<map_slice>

=cut

=head2 sort

    self.sort(block)

Returns a sorted array of the range elements. With a block, uses the block for custom comparison.

    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)

Returns an array sorted by the value returned from applying the block to each element.

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

=cut

=head2 step

    self.step

Returns the step size of the range. For ranges created without an explicit step, returns 1 or -1 depending on direction.

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

=cut

=head2 to

    self.to(to)

Creates a new range with the specified end bound, keeping the original start bound.

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

=cut

=head2 to_a

    self.to_a

Converts the range to an array containing all its elements. Forces evaluation of the lazy range.

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

Aliases: I<to_array>

=cut

=head2 to_v

    self.to_v

Converts the range to a vector (Vector object).

    var vec = (1..5).to_v

Aliases: I<to_vec>, I<to_vector>

=cut

=head2 unroll_operator

    self.unroll_operator(op, *args)

Applies an unroll operator operation over the range.

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

=cut

=head2 while

    self.while(block)

Iterates over the range while the block condition is true, returning an array of elements.

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

=cut

=head2 zip_operator

    self.zip_operator(op, *args)

Zips the range with other ranges or arrays, applying an operator to corresponding elements.

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

=cut

=head1 SEE ALSO

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

=head1 AUTHOR

Daniel Șuteu (trizen)

=cut
