6 Commits
Author SHA1 Message Date
ge ceed23ceed feat: add capacity(), is_valid(), remove is_empty(), add tests 2026-09-24 23:10:41 +03:00
ge d2c5ad5d2f version 0.6.0
* Check for zero step value to void infinite loops
* Add `new()` fn for creating ranges with checks
* Add `to_array()` method
* Add `is_empty()` method
* Add more tests
2026-07-15 01:09:33 +03:00
ge 02d06423b7 tests: Disable -no-skip-unused flag on tests, since https://github.com/vlang/v/issues/27147 is closed 2026-05-31 22:29:38 +03:00
ge 34ed972930 fix tests 2026-05-12 19:55:19 +03:00
ge a055360436 mod: bump version 2026-03-24 21:44:20 +03:00
ge 93a610fa02 feat: add bounds(), make Range private back 2026-03-24 21:43:46 +03:00
3 changed files with 237 additions and 26 deletions
+86 -12
View File
@@ -3,20 +3,21 @@ module ranges
import strconv
import math.big
@[noinit]
pub struct Range[T] {
pub:
struct Range[T] {
start T
end T
step T
is_neg bool // Is set to true if range end value is lesser than start value.
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.end) || (!r.is_neg && r.cur > r.end) {
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 {
@@ -31,7 +32,20 @@ pub fn (mut r Range[T]) reset() {
r.cur = r.start
}
// with_step returns copy of the range with new step value.
// 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
}
// capacity returns the calculated number of elements in range.
pub fn (r Range[T]) capacity() int {
if !r.is_valid() {
return 0
}
return r.capacity_no_valid_check()
}
// with_step returns copy of the range with updated step value.
pub fn (r Range[T]) with_step[T](step T) Range[T] {
return Range[T]{
...r
@@ -40,6 +54,47 @@ pub fn (r Range[T]) with_step[T](step T) Range[T] {
}
}
// to_array returns an array of elements from the range. This method is slightly
// faster than simply filling an array with elements in a loop, because the
// memory for the array is pre-allocated.
pub fn (r Range[T]) to_array() []T {
if !r.is_valid() {
return []T{}
}
cap := r.capacity_no_valid_check()
mut arr := []T{cap: cap}
for el in r {
arr << el
}
return arr
}
// is_valid reports is the range valid.
pub fn (r Range[T]) is_valid() bool {
zero := T{}
if r.step == zero {
return false
}
if r.step > zero && r.start > r.end {
return false
}
if r.step < zero && r.start <= r.end {
return false
}
return true
}
@[inline]
fn (r Range[T]) capacity_no_valid_check() int {
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()
}
return cap
}
// range creates new Range iterator with given start, end and step values.
//
// Generally numbers are expected. If type is a struct the following operators
@@ -48,14 +103,35 @@ pub fn (r Range[T]) with_step[T](step T) Range[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.
// Is it recommended to check range with `is_valid()`. 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]{
start: start
end: end
step: step
cur: start
is_neg: start > end
is_rev: start > end
}
}
@@ -147,8 +223,7 @@ pub fn from_string_custom[T](s string, conv StringConvertFn[T], config RangeFrom
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`' +
"formatted string expected, not '${s}'")
return error('`start${sep}end` or `start[:step]:end`' + "formatted string expected, not '${s}'")
}
if parts.len == 1 {
return [parts[0], parts[0], '1']
@@ -163,8 +238,7 @@ fn split_string(s string, sep string) ![]string {
if sep == ':' && parts.len == 3 {
return [parts[0], parts[2], parts[1]]
}
return error('invalid range string: expected `start[${sep}step]${sep}end` ' +
'or `start${sep}end[/step]` format, got `${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 {
+150 -13
View File
@@ -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),
@@ -111,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}')
}
@@ -179,3 +190,129 @@ fn test_range_new_with_step() {
}
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_valid() {
r := ranges.range(0, 0, -9000)
assert !r.is_valid()
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'
}
}
fn test_range_capacity() {
r := ranges.range(0, 9, 1)
assert r.capacity() == 10
}
fn test_range_capacity_non_one_step() {
r := ranges.range(0, 9, 2)
assert r.capacity() == 5
}
+1 -1
View File
@@ -1,7 +1,7 @@
Module {
name: 'ranges'
description: 'Operating with ranges of numbers'
version: '0.4.0'
version: '0.7.0'
license: 'Unlicense'
dependencies: []
}