6 Commits

Author SHA1 Message Date
ge
06f85c48d3 mod: bump version 2026-01-11 03:13:54 +03:00
ge
74e92c77ab feat: add with_step(), reset() 2026-01-11 03:13:01 +03:00
ge
b51f74a877 readme: fix 2025-12-30 15:43:51 +03:00
ge
b1eaeee6f0 readme: fix Range description 2025-12-30 12:30:04 +03:00
ge
899dacba7a mod: bump version 2025-12-30 12:27:08 +03:00
ge
4306b2220c breaking,fix: remove range exclusivity support
The usefulness of this feature is questionable, and its correct implementation
is difficult due to the use of generics. The implementation worked incorrectly
in cases where the iterator step is not equal to one and is not a multiple of
the end element. For example, for `range(0, 7, 4)`, the result is `[0]`
instead of `[0, 4]`. After this commit range end value is always included.

To check for multiples, a user-defined type must also implement an overload of
the remainder operator (`%`), even if the exclusivity function is not needed.

Another correct implementation requires subtracting one from the end element
for integers and the minimum fractional part supported by the type for floats.
This cannot be done correctly for generics, as it requires casting the literal
to a specific type, and we cannot cast number to any type.
2025-12-30 12:19:35 +03:00
4 changed files with 60 additions and 45 deletions

View File

@@ -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, a step size, and an inclusive/exclusive flag.
end points and a step size.
```v
import ranges

View File

@@ -4,7 +4,8 @@ import strconv
import math.big
struct Range[T] {
limit T
start T
end T
step T
is_neg bool
mut:
@@ -13,7 +14,7 @@ mut:
// 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.is_neg && r.cur < r.end) || (!r.is_neg && r.cur > r.end) {
return none
}
defer {
@@ -22,11 +23,21 @@ pub fn (mut r Range[T]) next() ?T {
return r.cur
}
@[params]
pub struct RangeConfig {
pub:
// If true exclude the end value from range.
exclusive bool
// 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
}
// 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]{
start: r.start
end: r.end
step: step
is_neg: r.is_neg
cur: r.start
}
}
// range creates new Range iterator with given start, end and step values.
@@ -35,17 +46,13 @@ pub:
// must be overloaded to perform comparisons and arithmetics: `+`, `-`, `<`, `==`.
// See https://docs.vlang.io/limited-operator-overloading.html for details.
//
// By default, the range includes the end value. This behavior can be changed
// by enabling the 'exclusive' option.
// The range includes the end value.
//
// Note: Zero step value will cause an infitite loop!
pub fn range[T](start T, end T, step T, config RangeConfig) Range[T] {
mut limit := end
if config.exclusive {
limit -= step
}
pub fn range[T](start T, end T, step T) Range[T] {
return Range[T]{
limit: limit
start: start
end: end
step: step
cur: start
is_neg: start > end
@@ -54,7 +61,6 @@ pub fn range[T](start T, end T, step T, config RangeConfig) Range[T] {
@[params]
pub struct RangeFromStringConfig {
RangeConfig
pub:
sep string = '-'
group_sep string = ','
@@ -65,7 +71,7 @@ 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
// Supported string formats are `start-end` 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'
@@ -92,7 +98,7 @@ pub fn from_string[T](s string, config RangeFromStringConfig) ![]Range[T] {
convert_string[T](range_str[0])!,
convert_string[T](range_str[1])!,
convert_string[T](range_str[2])!,
config.RangeConfig)
)
// vfmt on
}
return result
@@ -133,7 +139,7 @@ pub fn from_string_custom[T](s string, conv StringConvertFn[T], config RangeFrom
start := conv[T](range_str[0])!
end := conv[T](range_str[1])!
step := conv[T](range_str[2])!
result << range(start, end, step, config.RangeConfig)
result << range(start, end, step)
}
return result
}

View File

@@ -9,14 +9,6 @@ fn test_range() {
assert result == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
fn test_range_exclusive() {
mut result := []int{}
for i in ranges.range[int](0, 10, 1, exclusive: true) {
result << i
}
assert result == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
fn test_range_negative() {
mut result := []int{}
for i in ranges.range[int](10, 0, -1) {
@@ -25,14 +17,6 @@ fn test_range_negative() {
assert result == [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
}
fn test_range_negative_exclusive() {
mut result := []int{}
for i in ranges.range[int](10, 0, -1, exclusive: true) {
result << i
}
assert result == [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
}
fn test_range_with_step() {
mut result := []int{}
for i in ranges.range[int](0, 10, 2) {
@@ -65,14 +49,6 @@ fn test_range_single_item() {
assert result == [0]
}
fn test_range_single_item_exclusive() {
mut result := []int{}
for i in ranges.range(0, 1, 1, exclusive: true) {
result << i
}
assert result == [0]
}
fn test_range_bigint() {
start := big.zero_int
end := big.integer_from_int(5)
@@ -169,3 +145,36 @@ 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]
}

2
v.mod
View File

@@ -1,7 +1,7 @@
Module {
name: 'ranges'
description: 'Operating with ranges of numbers'
version: '0.1.0'
version: '0.3.0'
license: 'Unlicense'
dependencies: []
}