mirror of
https://github.com/gechandesu/ranges.git
synced 2026-07-15 09:00:03 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2c5ad5d2f | ||
|
|
02d06423b7 | ||
|
|
34ed972930 | ||
|
|
a055360436 | ||
|
|
93a610fa02 | ||
|
|
fe67a0c5a6 | ||
|
|
808870ff09 | ||
|
|
39314b474b | ||
|
|
06f85c48d3 | ||
|
|
74e92c77ab | ||
|
|
b51f74a877 |
@@ -3,7 +3,7 @@
|
||||
The `ranges` module provides tools for creating ranges of numbers.
|
||||
|
||||
Ranges are represented by the generic `Range` iterator, which has start and
|
||||
end points, and a step size.
|
||||
end points and a step size.
|
||||
|
||||
```v
|
||||
import ranges
|
||||
|
||||
@@ -4,16 +4,20 @@ import strconv
|
||||
import math.big
|
||||
|
||||
struct Range[T] {
|
||||
limit T
|
||||
start T
|
||||
end T
|
||||
step T
|
||||
is_neg bool
|
||||
is_rev bool // Is set to true if range end value is lesser than start value.
|
||||
mut:
|
||||
cur T
|
||||
}
|
||||
|
||||
// next returns the new element from range or none if range end is reached.
|
||||
pub fn (mut r Range[T]) next() ?T {
|
||||
if (r.is_neg && r.cur < r.limit) || (!r.is_neg && r.cur > r.limit) {
|
||||
if r.step == $zero(T) {
|
||||
return none
|
||||
}
|
||||
if (r.is_rev && r.cur < r.end) || (!r.is_rev && r.cur > r.end) {
|
||||
return none
|
||||
}
|
||||
defer {
|
||||
@@ -22,6 +26,53 @@ pub fn (mut r Range[T]) next() ?T {
|
||||
return r.cur
|
||||
}
|
||||
|
||||
// reset resets the internal iterator state to its initial value, after which the iterator can be reused.
|
||||
// Note: `for i in iter {` does not modify the internal iterator state, but direct `next()` call does.
|
||||
pub fn (mut r Range[T]) reset() {
|
||||
r.cur = r.start
|
||||
}
|
||||
|
||||
// bounds returns the start, end and step values of range.
|
||||
pub fn (r Range[T]) bounds() (T, T, T) {
|
||||
return r.start, r.end, r.step
|
||||
}
|
||||
|
||||
// with_step returns copy of the range with new step value.
|
||||
pub fn (r Range[T]) with_step[T](step T) Range[T] {
|
||||
return Range[T]{
|
||||
...r
|
||||
step: step
|
||||
cur: r.start
|
||||
}
|
||||
}
|
||||
|
||||
// to_array returns an array of elements from the range.
|
||||
pub fn (r Range[T]) to_array() []T {
|
||||
if r.is_empty() {
|
||||
return []T{}
|
||||
}
|
||||
|
||||
mut cap := 0
|
||||
|
||||
$if T is $int || T is $float {
|
||||
cap = int((r.end - r.start) / r.step + T(1))
|
||||
} $else $if T is big.Integer {
|
||||
cap = ((r.end - r.start) / r.step + big.one_int).int()
|
||||
}
|
||||
|
||||
mut arr := []T{cap: cap}
|
||||
for el in r {
|
||||
arr << el
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// is_empty reports is the range instance empty (has no values).
|
||||
pub fn (r Range[T]) is_empty() bool {
|
||||
empty := $zero(Range[T])
|
||||
return r == empty
|
||||
}
|
||||
|
||||
// range creates new Range iterator with given start, end and step values.
|
||||
//
|
||||
// Generally numbers are expected. If type is a struct the following operators
|
||||
@@ -30,13 +81,34 @@ pub fn (mut r Range[T]) next() ?T {
|
||||
//
|
||||
// The range includes the end value.
|
||||
//
|
||||
// Note: Zero step value will cause an infitite loop!
|
||||
// Note: If range cannot be created the empty range will be returned. See also `new()`.
|
||||
pub fn range[T](start T, end T, step T) Range[T] {
|
||||
return new(start, end, step) or { Range[T]{} }
|
||||
}
|
||||
|
||||
// new creates new range with given start, end and step values.
|
||||
// Unlike `range`, this function will return an error if:
|
||||
//
|
||||
// * step value is zero;
|
||||
// * step > 0, but start value is greather than end value;
|
||||
// * step < 0, but start value is lesser than end value.
|
||||
pub fn new[T](start T, end T, step T) !Range[T] {
|
||||
zero := $zero(T)
|
||||
if step == zero {
|
||||
return error('step value is zero')
|
||||
}
|
||||
if step > zero && start > end {
|
||||
return error('step is positive, but start value is greather than end value')
|
||||
}
|
||||
if step < zero && start <= end {
|
||||
return error('step is negative, but start value is lesser than or equals end value')
|
||||
}
|
||||
return Range[T]{
|
||||
limit: end
|
||||
start: start
|
||||
end: end
|
||||
step: step
|
||||
cur: start
|
||||
is_neg: start > end
|
||||
is_rev: start > end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,28 +124,28 @@ pub:
|
||||
// Use from_string_custom if you want to use custom type with special string
|
||||
// convertion rules.
|
||||
//
|
||||
// Supported string formats are `start-end`, `start[:step]end`. start and end
|
||||
// values are sepatared by 'sep' which is hypen (`-`) by default. Single number
|
||||
// will be interpreted as range of one element. Several ranges can be specified
|
||||
// in a line, separated by 'group_sep' (comma by default). 'sep' and 'group_sep'
|
||||
// can be overrided by user.
|
||||
// Supported string formats are `start-end[/step]` and `start[:step]:end`. start
|
||||
// and end values are sepatared by 'sep' which is hypen (`-`) by default. Single
|
||||
// number will be interpreted as range of one element. Several ranges can be
|
||||
// specified in a line, separated by 'group_sep' (comma by default). 'sep' and
|
||||
// 'group_sep' can be overrided by user.
|
||||
//
|
||||
// Some example input strings:
|
||||
//
|
||||
// * `5` - range from 5 to 5 (single element).
|
||||
// * `0-10` - range from 0 to 10.
|
||||
// * `0-100/5` - range in Cron-style syntax from 0 to 100 with step 5.
|
||||
// * `15:-1:0` - range in MathLab-style syntax from 15 to 0 with negative step -1.
|
||||
// * `1..8` - range from 1 to 8 with '..' sep.
|
||||
// * `0-7,64-71` - multiple ranges: from 0 to 7 and from 64 to 71.
|
||||
//
|
||||
// Only MathLab-style syntax allows you to specify a step directly in the string.
|
||||
// For all other cases, the step is equal to one.
|
||||
// If the step value is not specified, it will be set to one.
|
||||
//
|
||||
// Example: assert ranges.from_string[int]('1-7')! == [ranges.range(1, 7, 1)]
|
||||
pub fn from_string[T](s string, config RangeFromStringConfig) ![]Range[T] {
|
||||
mut result := []Range[T]{}
|
||||
for i in s.split(config.group_sep) {
|
||||
range_str := parse_string(i, config.sep)!
|
||||
range_str := split_string(i, config.sep)!
|
||||
// vfmt off
|
||||
result << range[T](
|
||||
convert_string[T](range_str[0])!,
|
||||
@@ -116,7 +188,7 @@ pub type StringConvertFn[T] = fn (s string) !T
|
||||
pub fn from_string_custom[T](s string, conv StringConvertFn[T], config RangeFromStringConfig) ![]Range[T] {
|
||||
mut result := []Range[T]{}
|
||||
for i in s.split(config.group_sep) {
|
||||
range_str := parse_string(i, config.sep)!
|
||||
range_str := split_string(i, config.sep)!
|
||||
start := conv[T](range_str[0])!
|
||||
end := conv[T](range_str[1])!
|
||||
step := conv[T](range_str[2])!
|
||||
@@ -125,7 +197,7 @@ pub fn from_string_custom[T](s string, conv StringConvertFn[T], config RangeFrom
|
||||
return result
|
||||
}
|
||||
|
||||
fn parse_string(s string, sep string) ![]string {
|
||||
fn split_string(s string, sep string) ![]string {
|
||||
parts := s.split(sep)
|
||||
if parts.any(|x| x.is_blank()) || parts.len !in [1, 2, 3] {
|
||||
return error('`start${sep}end` or `start[:step]:end`' +
|
||||
@@ -133,12 +205,19 @@ fn parse_string(s string, sep string) ![]string {
|
||||
}
|
||||
if parts.len == 1 {
|
||||
return [parts[0], parts[0], '1']
|
||||
} else if parts.len == 2 {
|
||||
}
|
||||
if parts.len == 2 {
|
||||
if parts[1].contains('/') {
|
||||
end, step := parts[1].split_once('/') or { '', '' }
|
||||
return [parts[0], end, step]
|
||||
}
|
||||
return [parts[0], parts[1], '1']
|
||||
} else if sep == ':' && parts.len == 3 {
|
||||
}
|
||||
if sep == ':' && parts.len == 3 {
|
||||
return [parts[0], parts[2], parts[1]]
|
||||
}
|
||||
return error('invalid range string: ${s}')
|
||||
return error('invalid range string: expected `start[${sep}step]${sep}end` ' +
|
||||
'or `start${sep}end[/step]` format, got `${s}`')
|
||||
}
|
||||
|
||||
fn convert_string[T](s string) !T {
|
||||
|
||||
+174
-13
@@ -69,10 +69,15 @@ fn test_range_bigint() {
|
||||
|
||||
fn test_range_from_string() {
|
||||
assert ranges.from_string[int]('0-10')! == [ranges.range(0, 10, 1)]
|
||||
assert ranges.from_string[int]('0-7,8-15')! == [ranges.range(0, 7, 1),
|
||||
ranges.range(8, 15, 1)]
|
||||
assert ranges.from_string[int]('0-6,7,8-15')! == [ranges.range(0, 6, 1),
|
||||
ranges.range(7, 7, 1), ranges.range(8, 15, 1)]
|
||||
assert ranges.from_string[int]('0-7,8-15')! == [
|
||||
ranges.range(0, 7, 1),
|
||||
ranges.range(8, 15, 1),
|
||||
]
|
||||
assert ranges.from_string[int]('0-6,7,8-15')! == [
|
||||
ranges.range(0, 6, 1),
|
||||
ranges.range(7, 7, 1),
|
||||
ranges.range(8, 15, 1),
|
||||
]
|
||||
assert ranges.from_string[i64]('5:2:15', sep: ':')! == [ranges.range[i64](5, 15, 2)]
|
||||
assert ranges.from_string[int]('100:-1:0', sep: ':')! == [
|
||||
ranges.range(100, 0, -1),
|
||||
@@ -87,6 +92,7 @@ fn test_range_from_string() {
|
||||
assert ranges.from_string[f32]('0.0..99.99', sep: '..')! == [
|
||||
ranges.range[f32](0.0, 99.99, 1),
|
||||
]
|
||||
assert ranges.from_string[int]('0-100/5')! == [ranges.range(0, 100, 5)]
|
||||
}
|
||||
|
||||
struct Int {
|
||||
@@ -110,23 +116,29 @@ fn (a Int) == (b Int) bool {
|
||||
}
|
||||
|
||||
fn test_range_custom_type() {
|
||||
// vfmt off
|
||||
mut result := []Int{}
|
||||
for i in ranges.range[Int](Int{ val: 0 }, Int{ val: 5 }, Int{ val: 1 }) {
|
||||
start := Int{0}
|
||||
end := Int{5}
|
||||
step := Int{1}
|
||||
for i in ranges.range[Int](start, end, step) {
|
||||
result << i
|
||||
}
|
||||
assert result == [Int{0}, Int{1}, Int{2}, Int{3}, Int{4}, Int{5}]
|
||||
// vfmt on
|
||||
assert result == [
|
||||
Int{0},
|
||||
Int{1},
|
||||
Int{2},
|
||||
Int{3},
|
||||
Int{4},
|
||||
Int{5},
|
||||
]
|
||||
}
|
||||
|
||||
//
|
||||
// Note this bug: https://github.com/vlang/v/issues/26156
|
||||
//
|
||||
|
||||
fn test_range_from_string_custom_type() {
|
||||
assert ranges.from_string_custom[Int]('0-5', fn (s string) !Int {
|
||||
if s.is_int() {
|
||||
return Int{ val: s.int() }
|
||||
return Int{
|
||||
val: s.int()
|
||||
}
|
||||
} else {
|
||||
return error('invalid integer value: ${s}')
|
||||
}
|
||||
@@ -145,3 +157,152 @@ fn test_range_from_string_custom_type() {
|
||||
sep: '..'
|
||||
)! == [ranges.range[Int](Int{0}, Int{10}, Int{1})]
|
||||
}
|
||||
|
||||
fn test_range_reset() {
|
||||
mut result := []int{}
|
||||
mut iter := ranges.range(0, 5, 1)
|
||||
|
||||
for {
|
||||
if elem := iter.next() {
|
||||
result << elem
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert result == [0, 1, 2, 3, 4, 5]
|
||||
|
||||
iter.reset()
|
||||
|
||||
result = []int{}
|
||||
|
||||
for i in iter {
|
||||
result << i
|
||||
}
|
||||
assert result == [0, 1, 2, 3, 4, 5]
|
||||
}
|
||||
|
||||
fn test_range_new_with_step() {
|
||||
mut result := []int{}
|
||||
mut iter := ranges.range(0, 5, 1)
|
||||
|
||||
for i in iter.with_step(2) {
|
||||
result << i
|
||||
}
|
||||
assert result == [0, 2, 4]
|
||||
}
|
||||
|
||||
fn test_range_bounds() {
|
||||
r := ranges.range(0, 10, 2)
|
||||
a, b, c := r.bounds()
|
||||
assert a == 0
|
||||
assert b == 10
|
||||
assert c == 2
|
||||
}
|
||||
|
||||
fn test_range_to_array_int() {
|
||||
r := ranges.range(0, 5, 1)
|
||||
assert r.to_array() == [0, 1, 2, 3, 4, 5]
|
||||
}
|
||||
|
||||
fn test_range_to_array_float() {
|
||||
r := ranges.range[f64](3.0, 3.14, 0.01)
|
||||
assert r.to_array() == [
|
||||
f64(3.0),
|
||||
3.01,
|
||||
3.0199999999999996,
|
||||
3.0299999999999994,
|
||||
3.039999999999999,
|
||||
3.049999999999999,
|
||||
3.0599999999999987,
|
||||
3.0699999999999985,
|
||||
3.0799999999999983,
|
||||
3.089999999999998,
|
||||
3.099999999999998,
|
||||
3.1099999999999977,
|
||||
3.1199999999999974,
|
||||
3.1299999999999972,
|
||||
3.139999999999997,
|
||||
]
|
||||
}
|
||||
|
||||
fn test_range_to_array_bigint() {
|
||||
r := ranges.range(big.zero_int, big.three_int, big.one_int)
|
||||
assert r.to_array() == [big.zero_int, big.one_int, big.two_int, big.three_int]
|
||||
}
|
||||
|
||||
fn test_range_empty() {
|
||||
r := ranges.range(0, 0, 0)
|
||||
assert r.to_array() == []
|
||||
}
|
||||
|
||||
fn test_range_empty_bigint() {
|
||||
r := ranges.range(big.zero_int, big.zero_int, big.zero_int)
|
||||
assert r.to_array() == []
|
||||
}
|
||||
|
||||
fn test_range_is_empty() {
|
||||
r := ranges.range(0, 0, -9000)
|
||||
assert r.is_empty()
|
||||
assert r.to_array() == []
|
||||
}
|
||||
|
||||
fn test_new() {
|
||||
r := ranges.new(0, 10, 1)!
|
||||
a, b, c := r.bounds()
|
||||
assert a == 0
|
||||
assert b == 10
|
||||
assert c == 1
|
||||
assert r.to_array() == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
|
||||
fn test_new_with_step() {
|
||||
r := ranges.new(0, 10, 2)!
|
||||
assert r.to_array() == [0, 2, 4, 6, 8, 10]
|
||||
}
|
||||
|
||||
fn test_new_reversed() {
|
||||
r := ranges.new(10, 0, -1)!
|
||||
assert r.to_array() == [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
|
||||
}
|
||||
|
||||
fn test_new_with_negative_step() {
|
||||
r := ranges.new(10, 0, -2)!
|
||||
assert r.to_array() == [10, 8, 6, 4, 2, 0]
|
||||
}
|
||||
|
||||
fn test_new_single_item() {
|
||||
r := ranges.new(5, 5, 1)!
|
||||
assert r.to_array() == [5]
|
||||
}
|
||||
|
||||
fn test_new_error_zero_step() {
|
||||
if _ := ranges.new(0, 10, 0) {
|
||||
assert false, 'expected error for zero step'
|
||||
} else {
|
||||
assert err.msg() == 'step value is zero'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_new_error_positive_step_start_gt_end() {
|
||||
if _ := ranges.new(10, 0, 1) {
|
||||
assert false, 'expected error for positive step with start > end'
|
||||
} else {
|
||||
assert err.msg() == 'step is positive, but start value is greather than end value'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_new_error_negative_step_start_lt_end() {
|
||||
if _ := ranges.new(0, 10, -1) {
|
||||
assert false, 'expected error for negative step with start <= end'
|
||||
} else {
|
||||
assert err.msg() == 'step is negative, but start value is lesser than or equals end value'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_new_error_negative_step_start_eq_end() {
|
||||
if _ := ranges.new(5, 5, -1) {
|
||||
assert false, 'expected error for negative step with start <= end'
|
||||
} else {
|
||||
assert err.msg() == 'step is negative, but start value is lesser than or equals end value'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user