mirror of
https://github.com/gechandesu/ranges.git
synced 2026-01-02 13:59:36 +03:00
init
This commit is contained in:
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.v]
|
||||
indent_style = tab
|
||||
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
* text=auto eol=lf
|
||||
*.bat eol=crlf
|
||||
|
||||
*.v linguist-language=V
|
||||
*.vv linguist-language=V
|
||||
*.vsh linguist-language=V
|
||||
v.mod linguist-language=V
|
||||
.vdocignore linguist-language=ignore
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Binaries for programs and plugins
|
||||
main
|
||||
ranges
|
||||
*.exe
|
||||
*.exe~
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Ignore binary output folders
|
||||
bin/
|
||||
|
||||
# Ignore common editor/system specific metadata
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
|
||||
# ENV
|
||||
.env
|
||||
|
||||
# vweb and database
|
||||
*.db
|
||||
*.js
|
||||
20
README.md
Normal file
20
README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Operating with Ranges of Numbers
|
||||
|
||||
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.
|
||||
|
||||
```v
|
||||
import ranges
|
||||
|
||||
// Iterate from 0 to 5 with step 2. Negative values also supported.
|
||||
for i in ranges.range(0, 5, 2) {
|
||||
println(i)
|
||||
}
|
||||
// 0
|
||||
// 2
|
||||
// 4
|
||||
```
|
||||
|
||||
See more usage examples in [ranges_test.v](ranges_test.v).
|
||||
22
UNLICENSE
Normal file
22
UNLICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of relinquishment in
|
||||
perpetuity of all present and future rights to this software under copyright
|
||||
law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
206
ranges.v
Normal file
206
ranges.v
Normal file
@@ -0,0 +1,206 @@
|
||||
module ranges
|
||||
|
||||
import strconv
|
||||
import math.big
|
||||
|
||||
struct Range[T] {
|
||||
limit T
|
||||
step T
|
||||
is_neg bool
|
||||
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) {
|
||||
return none
|
||||
}
|
||||
defer {
|
||||
r.cur += r.step
|
||||
}
|
||||
return r.cur
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct RangeConfig {
|
||||
pub:
|
||||
// If true exclude the end value from range.
|
||||
exclusive bool
|
||||
}
|
||||
|
||||
// range creates new Range iterator with given start, end and step values.
|
||||
//
|
||||
// Generally numbers are expected. If type is a struct the following operators
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
return Range[T]{
|
||||
limit: limit
|
||||
step: step
|
||||
cur: start
|
||||
is_neg: start > end
|
||||
}
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct RangeFromStringConfig {
|
||||
RangeConfig
|
||||
pub:
|
||||
sep string = '-'
|
||||
group_sep string = ','
|
||||
}
|
||||
|
||||
// from_string parses the input string and returns an array of iterators.
|
||||
// This function supports only V native number types and big.Integer.
|
||||
// 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.
|
||||
//
|
||||
// Some example input strings:
|
||||
//
|
||||
// * `5` - range from 5 to 5 (single element).
|
||||
// * `0-10` - range from 0 to 10.
|
||||
// * `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.
|
||||
//
|
||||
// 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)!
|
||||
// vfmt off
|
||||
result << 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
|
||||
}
|
||||
|
||||
pub type StringConvertFn[T] = fn (s string) !T
|
||||
|
||||
// from_string_custom parses the input string using `conv` function to convert
|
||||
// string values into numbers and returns an array of iterators. This is an extended
|
||||
// version of from_string with the same semanthics.
|
||||
// Example:
|
||||
// ```v
|
||||
// import math.big
|
||||
// import ranges
|
||||
//
|
||||
// conv := fn (s string) !big.Integer {
|
||||
// return big.integer_from_string(s)!
|
||||
// }
|
||||
//
|
||||
// for range in ranges.from_string_custom('0-3,8,11-13', conv)! {
|
||||
// for i in range {
|
||||
// println(i)
|
||||
// }
|
||||
// }
|
||||
// // 0
|
||||
// // 1
|
||||
// // 2
|
||||
// // 3
|
||||
// // 8
|
||||
// // 11
|
||||
// // 12
|
||||
// // 13
|
||||
// ```
|
||||
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)!
|
||||
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)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fn parse_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}'")
|
||||
}
|
||||
if parts.len == 1 {
|
||||
return [parts[0], parts[0], '1']
|
||||
} else if parts.len == 2 {
|
||||
return [parts[0], parts[1], '1']
|
||||
} else if sep == ':' && parts.len == 3 {
|
||||
return [parts[0], parts[2], parts[1]]
|
||||
}
|
||||
return error('invalid range string: ${s}')
|
||||
}
|
||||
|
||||
fn convert_string[T](s string) !T {
|
||||
$match T {
|
||||
int {
|
||||
return strconv.atoi(s)!
|
||||
}
|
||||
i8 {
|
||||
return strconv.atoi8(s)!
|
||||
}
|
||||
i16 {
|
||||
return strconv.atoi16(s)!
|
||||
}
|
||||
i32 {
|
||||
return strconv.atoi32(s)!
|
||||
}
|
||||
i64 {
|
||||
return strconv.atoi64(s)!
|
||||
}
|
||||
isize {
|
||||
return isize(strconv.atoi64(s)!)
|
||||
}
|
||||
u8 {
|
||||
return strconv.atou8(s)!
|
||||
}
|
||||
u16 {
|
||||
return strconv.atou16(s)!
|
||||
}
|
||||
u32 {
|
||||
return strconv.atou32(s)!
|
||||
}
|
||||
u64 {
|
||||
return strconv.atou64(s)!
|
||||
}
|
||||
usize {
|
||||
return usize(strconv.atou64(s)!)
|
||||
}
|
||||
f32 {
|
||||
return f32(strconv.atof64(s)!)
|
||||
}
|
||||
f64 {
|
||||
return strconv.atof64(s)!
|
||||
}
|
||||
big.Integer {
|
||||
return big.integer_from_string(s)!
|
||||
}
|
||||
$else {
|
||||
return error("cannot convert '${s}' to ${typeof[T]().name}")
|
||||
}
|
||||
}
|
||||
return error('unexpected string convert error')
|
||||
}
|
||||
171
ranges_test.v
Normal file
171
ranges_test.v
Normal file
@@ -0,0 +1,171 @@
|
||||
import ranges
|
||||
import math.big
|
||||
|
||||
fn test_range() {
|
||||
mut result := []int{}
|
||||
for i in ranges.range[int](0, 10, 1) {
|
||||
result << i
|
||||
}
|
||||
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) {
|
||||
result << i
|
||||
}
|
||||
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) {
|
||||
result << i
|
||||
}
|
||||
assert result == [0, 2, 4, 6, 8, 10]
|
||||
}
|
||||
|
||||
fn test_range_with_negative_step() {
|
||||
mut result := []int{}
|
||||
for i in ranges.range[int](5, 0, -1) {
|
||||
result << i
|
||||
}
|
||||
assert result == [5, 4, 3, 2, 1, 0]
|
||||
}
|
||||
|
||||
fn test_range_with_non_odd_step() {
|
||||
mut result := []int{}
|
||||
for i in ranges.range[int](0, 5, 2) {
|
||||
result << i
|
||||
}
|
||||
assert result == [0, 2, 4]
|
||||
}
|
||||
|
||||
fn test_range_single_item() {
|
||||
mut result := []int{}
|
||||
for i in ranges.range(0, 0, 1) {
|
||||
result << i
|
||||
}
|
||||
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)
|
||||
step := big.one_int
|
||||
mut result := []big.Integer{}
|
||||
for i in ranges.range[big.Integer](start, end, step) {
|
||||
result << i
|
||||
}
|
||||
assert result == [
|
||||
big.integer_from_int(0),
|
||||
big.integer_from_int(1),
|
||||
big.integer_from_int(2),
|
||||
big.integer_from_int(3),
|
||||
big.integer_from_int(4),
|
||||
big.integer_from_int(5),
|
||||
]
|
||||
}
|
||||
|
||||
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[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),
|
||||
]
|
||||
assert ranges.from_string[int]('1..10', sep: '..')! == [ranges.range(1, 10, 1)]
|
||||
assert ranges.from_string[int]('-256..256', sep: '..')! == [
|
||||
ranges.range(-256, 256, 1),
|
||||
]
|
||||
assert ranges.from_string[int]('256..-256', sep: '..')! == [
|
||||
ranges.range(256, -256, 1),
|
||||
]
|
||||
assert ranges.from_string[f32]('0.0..99.99', sep: '..')! == [
|
||||
ranges.range[f32](0.0, 99.99, 1),
|
||||
]
|
||||
}
|
||||
|
||||
struct Int {
|
||||
val int
|
||||
}
|
||||
|
||||
fn (a Int) + (b Int) Int {
|
||||
return Int{a.val + b.val}
|
||||
}
|
||||
|
||||
fn (a Int) - (b Int) Int {
|
||||
return Int{a.val - b.val}
|
||||
}
|
||||
|
||||
fn (a Int) < (b Int) bool {
|
||||
return a.val < b.val
|
||||
}
|
||||
|
||||
fn (a Int) == (b Int) bool {
|
||||
return a.val == b.val
|
||||
}
|
||||
|
||||
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 }) {
|
||||
result << i
|
||||
}
|
||||
assert result == [Int{0}, Int{1}, Int{2}, Int{3}, Int{4}, Int{5}]
|
||||
// vfmt on
|
||||
}
|
||||
|
||||
//
|
||||
// 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() }
|
||||
} else {
|
||||
return error('invalid integer value: ${s}')
|
||||
}
|
||||
})! == [ranges.range[Int](Int{0}, Int{5}, Int{1})]
|
||||
|
||||
convert_fn := fn (s string) !Int {
|
||||
if s.is_int() {
|
||||
return Int{
|
||||
val: s.int()
|
||||
}
|
||||
} else {
|
||||
return error('invalid integer value: ${s}')
|
||||
}
|
||||
}
|
||||
assert ranges.from_string_custom[Int]('0..10', convert_fn,
|
||||
sep: '..'
|
||||
)! == [ranges.range[Int](Int{0}, Int{10}, Int{1})]
|
||||
}
|
||||
Reference in New Issue
Block a user