mirror of
https://github.com/gechandesu/ranges.git
synced 2026-01-02 13:59:36 +03:00
207 lines
5.0 KiB
V
207 lines
5.0 KiB
V
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')
|
|
}
|