From 8e36ab055162efa6f67f3b9ee62f625ac8874901 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 21 Sep 2021 10:59:16 -0400 Subject: [PATCH] bytes, strings: add Cut 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 #46336. Change-Id: Ia418ed7c3706c65bf61e1b2c5baf534cb783e4d3 Reviewed-on: https://go-review.googlesource.com/c/go/+/351710 Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Go Bot Reviewed-by: Ian Lance Taylor --- src/bytes/bytes.go | 13 ++++ src/bytes/bytes_test.go | 23 ++++++ src/bytes/example_test.go | 138 ++++++++++++++++++------------------ src/strings/example_test.go | 58 +++++++++------ src/strings/strings.go | 11 +++ src/strings/strings_test.go | 25 ++++++- 6 files changed, 178 insertions(+), 90 deletions(-) diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index cd859d086db09..a9f10031c4a4e 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -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 +} diff --git a/src/bytes/bytes_test.go b/src/bytes/bytes_test.go index 850b2ed061d8b..3bece6adf0b90 100644 --- a/src/bytes/bytes_test.go +++ b/src/bytes/bytes_test.go @@ -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 { diff --git a/src/bytes/example_test.go b/src/bytes/example_test.go index d0d4dd2c2d43b..d04b088fabbc4 100644 --- a/src/bytes/example_test.go +++ b/src/bytes/example_test.go @@ -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"))) @@ -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++"))) @@ -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"))) @@ -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"))) @@ -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() { @@ -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: @@ -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: @@ -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 @@ -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 } diff --git a/src/strings/example_test.go b/src/strings/example_test.go index 375f9cac6502e..94aa167f90900 100644 --- a/src/strings/example_test.go +++ b/src/strings/example_test.go @@ -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() { @@ -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")) @@ -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 -} diff --git a/src/strings/strings.go b/src/strings/strings.go index 0df8d2eb281dc..4b543dcc1acae 100644 --- a/src/strings/strings.go +++ b/src/strings/strings.go @@ -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 +} diff --git a/src/strings/strings_test.go b/src/strings/strings_test.go index edc6c205907d3..0f30ca738e675 100644 --- a/src/strings/strings_test.go +++ b/src/strings/strings_test.go @@ -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) } } }