Skip to content

Commit

Permalink
bytes, strings: add Cut
Browse files Browse the repository at this point in the history
Using Cut is a clearer way to write the vast majority (>70%)
of existing code that calls Index, IndexByte, IndexRune, and SplitN.
There is more discussion on https://golang.org/issue/46336.

Fixes golang#46336.

Change-Id: Ia418ed7c3706c65bf61e1b2c5baf534cb783e4d3
Reviewed-on: https://go-review.googlesource.com/c/go/+/351710
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
rsc committed Oct 6, 2021
1 parent 810b08b commit 8e36ab0
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 90 deletions.
13 changes: 13 additions & 0 deletions src/bytes/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,3 +1192,16 @@ func Index(s, sep []byte) int {
}
return -1
}

// Cut slices s around the first instance of sep,
// returning the text before and after sep.
// The found result reports whether sep appears in s.
// If sep does not appear in s, cut returns s, "", false.
//
// Cut returns slices of the original slice s, not copies.
func Cut(s, sep []byte) (before, after []byte, found bool) {
if i := Index(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, nil, false
}
23 changes: 23 additions & 0 deletions src/bytes/bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,29 @@ func TestEqualFold(t *testing.T) {
}
}

var cutTests = []struct {
s, sep string
before, after string
found bool
}{
{"abc", "b", "a", "c", true},
{"abc", "a", "", "bc", true},
{"abc", "c", "ab", "", true},
{"abc", "abc", "", "", true},
{"abc", "", "", "abc", true},
{"abc", "d", "abc", "", false},
{"", "d", "", "", false},
{"", "", "", "", true},
}

func TestCut(t *testing.T) {
for _, tt := range cutTests {
if before, after, found := Cut([]byte(tt.s), []byte(tt.sep)); string(before) != tt.before || string(after) != tt.after || found != tt.found {
t.Errorf("Cut(%q, %q) = %q, %q, %v, want %q, %q, %v", tt.s, tt.sep, before, after, found, tt.before, tt.after, tt.found)
}
}
}

func TestBufferGrowNegative(t *testing.T) {
defer func() {
if err := recover(); err == nil {
Expand Down
138 changes: 70 additions & 68 deletions src/bytes/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,36 +105,6 @@ func ExampleCompare_search() {
}
}

func ExampleTrimSuffix() {
var b = []byte("Hello, goodbye, etc!")
b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
b = bytes.TrimSuffix(b, []byte("gopher"))
b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
os.Stdout.Write(b)
// Output: Hello, world!
}

func ExampleTrimPrefix() {
var b = []byte("Goodbye,, world!")
b = bytes.TrimPrefix(b, []byte("Goodbye,"))
b = bytes.TrimPrefix(b, []byte("See ya,"))
fmt.Printf("Hello%s", b)
// Output: Hello, world!
}

func ExampleFields() {
fmt.Printf("Fields are: %q", bytes.Fields([]byte(" foo bar baz ")))
// Output: Fields are: ["foo" "bar" "baz"]
}

func ExampleFieldsFunc() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", bytes.FieldsFunc([]byte(" foo1;bar2,baz3..."), f))
// Output: Fields are: ["foo1" "bar2" "baz3"]
}

func ExampleContains() {
fmt.Println(bytes.Contains([]byte("seafood"), []byte("foo")))
fmt.Println(bytes.Contains([]byte("seafood"), []byte("bar")))
Expand Down Expand Up @@ -181,6 +151,22 @@ func ExampleCount() {
// 5
}

func ExampleCut() {
show := func(s, sep string) {
before, after, found := bytes.Cut([]byte(s), []byte(sep))
fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
}
show("Gopher", "Go")
show("Gopher", "ph")
show("Gopher", "er")
show("Gopher", "Badger")
// Output:
// Cut("Gopher", "Go") = "", "pher", true
// Cut("Gopher", "ph") = "Go", "er", true
// Cut("Gopher", "er") = "Goph", "", true
// Cut("Gopher", "Badger") = "Gopher", "", false
}

func ExampleEqual() {
fmt.Println(bytes.Equal([]byte("Go"), []byte("Go")))
fmt.Println(bytes.Equal([]byte("Go"), []byte("C++")))
Expand All @@ -194,6 +180,19 @@ func ExampleEqualFold() {
// Output: true
}

func ExampleFields() {
fmt.Printf("Fields are: %q", bytes.Fields([]byte(" foo bar baz ")))
// Output: Fields are: ["foo" "bar" "baz"]
}

func ExampleFieldsFunc() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", bytes.FieldsFunc([]byte(" foo1;bar2,baz3..."), f))
// Output: Fields are: ["foo1" "bar2" "baz3"]
}

func ExampleHasPrefix() {
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("Go")))
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("C")))
Expand Down Expand Up @@ -259,6 +258,12 @@ func ExampleIndexRune() {
// -1
}

func ExampleJoin() {
s := [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}
fmt.Printf("%s", bytes.Join(s, []byte(", ")))
// Output: foo, bar, baz
}

func ExampleLastIndex() {
fmt.Println(bytes.Index([]byte("go gopher"), []byte("go")))
fmt.Println(bytes.LastIndex([]byte("go gopher"), []byte("go")))
Expand Down Expand Up @@ -299,10 +304,12 @@ func ExampleLastIndexFunc() {
// -1
}

func ExampleJoin() {
s := [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}
fmt.Printf("%s", bytes.Join(s, []byte(", ")))
// Output: foo, bar, baz
func ExampleReader_Len() {
fmt.Println(bytes.NewReader([]byte("Hi!")).Len())
fmt.Println(bytes.NewReader([]byte("こんにちは!")).Len())
// Output:
// 3
// 16
}

func ExampleRepeat() {
Expand Down Expand Up @@ -412,20 +419,6 @@ func ExampleTrimFunc() {
// go-gopher!
}

func ExampleMap() {
rot13 := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return 'A' + (r-'A'+13)%26
case r >= 'a' && r <= 'z':
return 'a' + (r-'a'+13)%26
}
return r
}
fmt.Printf("%s", bytes.Map(rot13, []byte("'Twas brillig and the slithy gopher...")))
// Output: 'Gjnf oevyyvt naq gur fyvgul tbcure...
}

func ExampleTrimLeft() {
fmt.Print(string(bytes.TrimLeft([]byte("453gopher8257"), "0123456789")))
// Output:
Expand All @@ -442,11 +435,28 @@ func ExampleTrimLeftFunc() {
// go-gopher!567
}

func ExampleTrimPrefix() {
var b = []byte("Goodbye,, world!")
b = bytes.TrimPrefix(b, []byte("Goodbye,"))
b = bytes.TrimPrefix(b, []byte("See ya,"))
fmt.Printf("Hello%s", b)
// Output: Hello, world!
}

func ExampleTrimSpace() {
fmt.Printf("%s", bytes.TrimSpace([]byte(" \t\n a lone gopher \n\t\r\n")))
// Output: a lone gopher
}

func ExampleTrimSuffix() {
var b = []byte("Hello, goodbye, etc!")
b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
b = bytes.TrimSuffix(b, []byte("gopher"))
b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
os.Stdout.Write(b)
// Output: Hello, world!
}

func ExampleTrimRight() {
fmt.Print(string(bytes.TrimRight([]byte("453gopher8257"), "0123456789")))
// Output:
Expand All @@ -463,21 +473,6 @@ func ExampleTrimRightFunc() {
// 1234go-gopher!
}

func ExampleToUpper() {
fmt.Printf("%s", bytes.ToUpper([]byte("Gopher")))
// Output: GOPHER
}

func ExampleToUpperSpecial() {
str := []byte("ahoj vývojári golang")
totitle := bytes.ToUpperSpecial(unicode.AzeriCase, str)
fmt.Println("Original : " + string(str))
fmt.Println("ToUpper : " + string(totitle))
// Output:
// Original : ahoj vývojári golang
// ToUpper : AHOJ VÝVOJÁRİ GOLANG
}

func ExampleToLower() {
fmt.Printf("%s", bytes.ToLower([]byte("Gopher")))
// Output: gopher
Expand All @@ -493,10 +488,17 @@ func ExampleToLowerSpecial() {
// ToLower : ahoj vývojári golang
}

func ExampleReader_Len() {
fmt.Println(bytes.NewReader([]byte("Hi!")).Len())
fmt.Println(bytes.NewReader([]byte("こんにちは!")).Len())
func ExampleToUpper() {
fmt.Printf("%s", bytes.ToUpper([]byte("Gopher")))
// Output: GOPHER
}

func ExampleToUpperSpecial() {
str := []byte("ahoj vývojári golang")
totitle := bytes.ToUpperSpecial(unicode.AzeriCase, str)
fmt.Println("Original : " + string(str))
fmt.Println("ToUpper : " + string(totitle))
// Output:
// 3
// 16
// Original : ahoj vývojári golang
// ToUpper : AHOJ VÝVOJÁRİ GOLANG
}
58 changes: 37 additions & 21 deletions src/strings/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@ import (
"unicode"
)

func ExampleFields() {
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
// Output: Fields are: ["foo" "bar" "baz"]
}

func ExampleFieldsFunc() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
func ExampleBuilder() {
var b strings.Builder
for i := 3; i >= 1; i-- {
fmt.Fprintf(&b, "%d...", i)
}
fmt.Printf("Fields are: %q", strings.FieldsFunc(" foo1;bar2,baz3...", f))
// Output: Fields are: ["foo1" "bar2" "baz3"]
b.WriteString("ignition")
fmt.Println(b.String())

// Output: 3...2...1...ignition
}

func ExampleCompare() {
Expand Down Expand Up @@ -79,11 +77,40 @@ func ExampleCount() {
// 5
}

func ExampleCut() {
show := func(s, sep string) {
before, after, found := strings.Cut(s, sep)
fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
}
show("Gopher", "Go")
show("Gopher", "ph")
show("Gopher", "er")
show("Gopher", "Badger")
// Output:
// Cut("Gopher", "Go") = "", "pher", true
// Cut("Gopher", "ph") = "Go", "er", true
// Cut("Gopher", "er") = "Goph", "", true
// Cut("Gopher", "Badger") = "Gopher", "", false
}

func ExampleEqualFold() {
fmt.Println(strings.EqualFold("Go", "go"))
// Output: true
}

func ExampleFields() {
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
// Output: Fields are: ["foo" "bar" "baz"]
}

func ExampleFieldsFunc() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", strings.FieldsFunc(" foo1;bar2,baz3...", f))
// Output: Fields are: ["foo1" "bar2" "baz3"]
}

func ExampleHasPrefix() {
fmt.Println(strings.HasPrefix("Gopher", "Go"))
fmt.Println(strings.HasPrefix("Gopher", "C"))
Expand Down Expand Up @@ -370,14 +397,3 @@ func ExampleTrimRightFunc() {
}))
// Output: ¡¡¡Hello, Gophers
}

func ExampleBuilder() {
var b strings.Builder
for i := 3; i >= 1; i-- {
fmt.Fprintf(&b, "%d...", i)
}
b.WriteString("ignition")
fmt.Println(b.String())

// Output: 3...2...1...ignition
}
11 changes: 11 additions & 0 deletions src/strings/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -1118,3 +1118,14 @@ func Index(s, substr string) int {
}
return -1
}

// Cut slices s around the first instance of sep,
// returning the text before and after sep.
// The found result reports whether sep appears in s.
// If sep does not appear in s, cut returns s, "", false.
func Cut(s, sep string) (before, after string, found bool) {
if i := Index(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, "", false
}
25 changes: 24 additions & 1 deletion src/strings/strings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1579,7 +1579,30 @@ var CountTests = []struct {
func TestCount(t *testing.T) {
for _, tt := range CountTests {
if num := Count(tt.s, tt.sep); num != tt.num {
t.Errorf("Count(\"%s\", \"%s\") = %d, want %d", tt.s, tt.sep, num, tt.num)
t.Errorf("Count(%q, %q) = %d, want %d", tt.s, tt.sep, num, tt.num)
}
}
}

var cutTests = []struct {
s, sep string
before, after string
found bool
}{
{"abc", "b", "a", "c", true},
{"abc", "a", "", "bc", true},
{"abc", "c", "ab", "", true},
{"abc", "abc", "", "", true},
{"abc", "", "", "abc", true},
{"abc", "d", "abc", "", false},
{"", "d", "", "", false},
{"", "", "", "", true},
}

func TestCut(t *testing.T) {
for _, tt := range cutTests {
if before, after, found := Cut(tt.s, tt.sep); before != tt.before || after != tt.after || found != tt.found {
t.Errorf("Cut(%q, %q) = %q, %q, %v, want %q, %q, %v", tt.s, tt.sep, before, after, found, tt.before, tt.after, tt.found)
}
}
}
Expand Down

0 comments on commit 8e36ab0

Please sign in to comment.