diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index 02f1d8c833..941a263250 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -757,6 +757,36 @@ pub fn (s string) split_any(delim string) []string { return res } +// rsplit_any splits the string to an array by any of the `delim` chars in reverse order. +// Example: "first row\nsecond row".rsplit_any(" \n") == ['row', 'second', 'row', 'first'] +// Split a string using the chars in the delimiter string as delimiters chars. +// If the delimiter string is empty then `.rsplit()` is used. +[direct_array_access] +pub fn (s string) rsplit_any(delim string) []string { + mut res := []string{} + mut i := s.len - 1 + if s.len > 0 { + if delim.len <= 0 { + return s.rsplit('') + } + mut rbound := s.len + for i >= 0 { + for delim_ch in delim { + if s[i] == delim_ch { + res << s[i + 1..rbound] + rbound = i + break + } + } + i-- + } + if rbound > 0 { + res << s[..rbound] + } + } + return res +} + // split splits the string to an array by `delim`. // Example: assert 'A B C'.split(' ') == ['A','B','C'] // If `delim` is empty the string is split by it's characters. @@ -765,6 +795,52 @@ pub fn (s string) split(delim string) []string { return s.split_nth(delim, 0) } +// rsplit splits the string to an array by `delim` in reverse order. +// Example: assert 'A B C'.rsplit(' ') == ['C','B','A'] +// If `delim` is empty the string is split by it's characters. +// Example: assert 'DEF'.rsplit('') == ['F','E','D'] +pub fn (s string) rsplit(delim string) []string { + return s.rsplit_nth(delim, 0) +} + +// split_once devides string into pair of string by `delim`. +// Example: +// ```v +// path, ext := 'file.ts.dts'.splice_once('.')? +// assert path == 'file' +// assert ext == 'ts.dts' +// ``` +// Note that rsplit_once returns splitted string string as first part of pair, +// and returns remaining as second part of pair. +pub fn (s string) split_once(delim string) ?(string, string) { + result := s.split_nth(delim, 2) + + if result.len != 2 { + return none + } + + return result[0], result[1] +} + +// rsplit_once devides string into pair of string by `delim`. +// Example: +// ```v +// path, ext := 'file.ts.dts'.splice_once('.')? +// assert path == 'file.ts' +// assert ext == 'dts' +// ``` +// Note that rsplit_once returns remaining string as first part of pair, +// and returns splitted string as second part of pair. +pub fn (s string) rsplit_once(delim string) ?(string, string) { + result := s.rsplit_nth(delim, 2) + + if result.len != 2 { + return none + } + + return result[1], result[0] +} + // split_nth splits the string based on the passed `delim` substring. // It returns the first Nth parts. When N=0, return all the splits. // The last returned element has the remainder of the string, even if @@ -839,6 +915,74 @@ pub fn (s string) split_nth(delim string, nth int) []string { } } +// rsplit_nth splits the string based on the passed `delim` substring in revese order. +// It returns the first Nth parts. When N=0, return all the splits. +// The last returned element has the remainder of the string, even if +// the remainder contains more `delim` substrings. +[direct_array_access] +pub fn (s string) rsplit_nth(delim string, nth int) []string { + mut res := []string{} + mut i := s.len - 1 + + match delim.len { + 0 { + for i >= 0 { + if nth > 0 && res.len == nth - 1 { + res << s[..i] + break + } + res << s[i].ascii_str() + i-- + } + return res + } + 1 { + mut rbound := s.len + delim_byte := delim[0] + + for i >= 0 { + if s[i] == delim_byte { + if nth > 0 && res.len == nth - 1 { + break + } + res << s[i + 1..rbound] + rbound = i + i-- + } else { + i-- + } + } + + if nth < 1 || res.len < nth { + res << s[..rbound] + } + return res + } + else { + mut rbound := s.len + + for i >= 0 { + is_delim := i - delim.len >= 0 && s[i - delim.len..i] == delim + if is_delim { + if nth > 0 && res.len == nth - 1 { + break + } + res << s[i..rbound] + rbound = i - delim.len + i -= delim.len + } else { + i-- + } + } + + if nth < 1 || res.len < nth { + res << s[..rbound] + } + return res + } + } +} + // split_into_lines splits the string by newline characters. // newlines are stripped. // `\r` (MacOS), `\n` (POSIX), and `\r\n` (WinOS) line endings are all supported (including mixed line endings). diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index 3fded6b666..e683e9ca66 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -188,6 +188,37 @@ fn test_split_nth() { assert e.split_nth(',', 3).len == 3 } +fn test_rsplit_nth() { + a := '1,2,3' + assert a.rsplit(',').len == 3 + assert a.rsplit_nth(',', -1).len == 3 + assert a.rsplit_nth(',', 0).len == 3 + assert a.rsplit_nth(',', 1).len == 1 + assert a.rsplit_nth(',', 2).len == 2 + assert a.rsplit_nth(',', 10).len == 3 + b := '1::2::3' + assert b.rsplit('::').len == 3 + assert b.rsplit_nth('::', -1).len == 3 + assert b.rsplit_nth('::', 0).len == 3 + assert b.rsplit_nth('::', 1).len == 1 + assert b.rsplit_nth('::', 2).len == 2 + assert b.rsplit_nth('::', 10).len == 3 + c := 'ABCDEF' + assert c.rsplit('').len == 6 + assert c.rsplit_nth('', 3).len == 3 + assert c.rsplit_nth('BC', -1).len == 2 + d := ',' + assert d.rsplit(',').len == 2 + assert d.rsplit_nth('', 3).len == 1 + assert d.rsplit_nth(',', -1).len == 2 + assert d.rsplit_nth(',', 3).len == 2 + e := ',,,0,,,,,a,,b,' + assert e.rsplit(',,').len == 5 + assert e.rsplit_nth(',,', 3).len == 3 + assert e.rsplit_nth(',', -1).len == 12 + assert e.rsplit_nth(',', 3).len == 3 +} + fn test_split_nth_values() { line := 'CMD=eprintln(phase=1)' @@ -219,6 +250,37 @@ fn test_split_nth_values() { assert a4[2] == '1)' } +fn test_rsplit_nth_values() { + line := 'CMD=eprintln(phase=1)' + + a0 := line.rsplit_nth('=', 0) + assert a0.len == 3 + assert a0[0] == '1)' + assert a0[1] == 'eprintln(phase' + assert a0[2] == 'CMD' + + a1 := line.rsplit_nth('=', 1) + assert a1.len == 1 + assert a1[0] == 'CMD=eprintln(phase=1)' + + a2 := line.rsplit_nth('=', 2) + assert a2.len == 2 + assert a2[0] == '1)' + assert a2[1] == 'CMD=eprintln(phase' + + a3 := line.rsplit_nth('=', 3) + assert a3.len == 3 + assert a0[0] == '1)' + assert a0[1] == 'eprintln(phase' + assert a0[2] == 'CMD' + + a4 := line.rsplit_nth('=', 4) + assert a4.len == 3 + assert a0[0] == '1)' + assert a0[1] == 'eprintln(phase' + assert a0[2] == 'CMD' +} + fn test_split() { mut s := 'volt/twitch.v:34' mut vals := s.split(':') @@ -265,6 +327,52 @@ fn test_split() { assert vals[1] == '' } +fn test_rsplit() { + mut s := 'volt/twitch.v:34' + mut vals := s.rsplit(':') + assert vals.len == 2 + assert vals[0] == '34' + assert vals[1] == 'volt/twitch.v' + // ///////// + s = '2018-01-01z13:01:02' + vals = s.rsplit('z') + assert vals.len == 2 + assert vals[0] == '13:01:02' + assert vals[1] == '2018-01-01' + // ////////// + s = '4627a862c3dec29fb3182a06b8965e0025759e18___1530207969___blue' + vals = s.rsplit('___') + assert vals.len == 3 + assert vals[0] == 'blue' + assert vals[1] == '1530207969' + assert vals[2] == '4627a862c3dec29fb3182a06b8965e0025759e18' + // ///////// + s = 'lalala' + vals = s.rsplit('a') + assert vals.len == 4 + assert vals[0] == '' + assert vals[1] == 'l' + assert vals[2] == 'l' + assert vals[3] == 'l' + // ///////// + s = 'awesome' + a := s.rsplit('') + assert a.len == 7 + assert a[0] == 'e' + assert a[1] == 'm' + assert a[2] == 'o' + assert a[3] == 's' + assert a[4] == 'e' + assert a[5] == 'w' + assert a[6] == 'a' + // ///////// + s = 'wavy turquoise bags' + vals = s.rsplit('wavy ') + assert vals.len == 2 + assert vals[0] == 'turquoise bags' + assert vals[1] == '' +} + fn test_split_any() { assert 'ABC'.split_any('') == ['A', 'B', 'C'] assert ''.split_any(' ') == [] @@ -276,6 +384,41 @@ fn test_split_any() { assert 'first row\nsecond row'.split_any(' \n') == ['first', 'row', 'second', 'row'] } +fn test_rsplit_any() { + assert 'ABC'.rsplit_any('') == ['C', 'B', 'A'] + assert ''.rsplit_any(' ') == [] + assert ' '.rsplit_any(' ') == [''] + assert ' '.rsplit_any(' ') == ['', ''] + assert ' Ciao come stai?'.rsplit_any(' ') == ['stai?', 'come', 'Ciao'] + assert ' Ciao+come*stai?'.rsplit_any('+*') == ['stai?', 'come', ' Ciao'] + assert ' Ciao+come*stai?'.rsplit_any('+* ') == ['stai?', 'come', 'Ciao'] + assert 'first row\nsecond row'.rsplit_any(' \n') == ['row', 'second', 'row', 'first'] +} + +fn test_split_once() ? { + path1, ext1 := 'home/dir/lang.zip'.split_once('.')? + assert path1 == 'home/dir/lang' + assert ext1 == 'zip' + path2, ext2 := 'home/dir/lang.ts.dts'.split_once('.')? + assert path2 == 'home/dir/lang' + assert ext2 == 'ts.dts' + path3, ext3 := 'home/dir'.split_once('.') or { '', '' } + assert path3 == '' + assert ext3 == '' +} + +fn test_rsplit_once() ? { + path1, ext1 := 'home/dir/lang.zip'.rsplit_once('.')? + assert path1 == 'home/dir/lang' + assert ext1 == 'zip' + path2, ext2 := 'home/dir/lang.ts.dts'.rsplit_once('.')? + assert path2 == 'home/dir/lang.ts' + assert ext2 == 'dts' + path3, ext3 := 'home/dir'.rsplit_once('.') or { '', '' } + assert path3 == '' + assert ext3 == '' +} + fn test_trim_space() { a := ' a ' assert a.trim_space() == 'a'