mirror of
https://github.com/golang/go
synced 2024-11-26 00:07:57 -07:00
strings: fix NewReplacer(old0, new0, old1, new1, ...) to be consistent
when oldi == oldj. Benchmark numbers show no substantial change. R=eric.d.eisner, rogpeppe CC=golang-dev https://golang.org/cl/6496104
This commit is contained in:
parent
e39072d65f
commit
6910356ea8
@ -33,47 +33,41 @@ func NewReplacer(oldnew ...string) *Replacer {
|
|||||||
panic("strings.NewReplacer: odd argument count")
|
panic("strings.NewReplacer: odd argument count")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Possible implementations.
|
allNewBytes := true
|
||||||
var (
|
for i := 0; i < len(oldnew); i += 2 {
|
||||||
bb byteReplacer
|
if len(oldnew[i]) != 1 {
|
||||||
bs byteStringReplacer
|
return &Replacer{r: makeGenericReplacer(oldnew)}
|
||||||
gen genericReplacer
|
|
||||||
)
|
|
||||||
|
|
||||||
allOldBytes, allNewBytes := true, true
|
|
||||||
for len(oldnew) > 0 {
|
|
||||||
old, new := oldnew[0], oldnew[1]
|
|
||||||
oldnew = oldnew[2:]
|
|
||||||
if len(old) != 1 {
|
|
||||||
allOldBytes = false
|
|
||||||
}
|
}
|
||||||
if len(new) != 1 {
|
if len(oldnew[i+1]) != 1 {
|
||||||
allNewBytes = false
|
allNewBytes = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic
|
|
||||||
gen.p = append(gen.p, pair{old, new})
|
|
||||||
|
|
||||||
// byte -> string
|
|
||||||
if allOldBytes {
|
|
||||||
bs.old.set(old[0])
|
|
||||||
bs.new[old[0]] = []byte(new)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// byte -> byte
|
if allNewBytes {
|
||||||
if allOldBytes && allNewBytes {
|
bb := &byteReplacer{}
|
||||||
bb.old.set(old[0])
|
for i := 0; i < len(oldnew); i += 2 {
|
||||||
bb.new[old[0]] = new[0]
|
o, n := oldnew[i][0], oldnew[i+1][0]
|
||||||
|
if bb.old[o>>5]&uint32(1<<(o&31)) != 0 {
|
||||||
|
// Later old->new maps do not override previous ones with the same old string.
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
bb.old.set(o)
|
||||||
|
bb.new[o] = n
|
||||||
|
}
|
||||||
|
return &Replacer{r: bb}
|
||||||
}
|
}
|
||||||
|
|
||||||
if allOldBytes && allNewBytes {
|
bs := &byteStringReplacer{}
|
||||||
return &Replacer{r: &bb}
|
for i := 0; i < len(oldnew); i += 2 {
|
||||||
|
o, new := oldnew[i][0], oldnew[i+1]
|
||||||
|
if bs.old[o>>5]&uint32(1<<(o&31)) != 0 {
|
||||||
|
// Later old->new maps do not override previous ones with the same old string.
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if allOldBytes {
|
bs.old.set(o)
|
||||||
return &Replacer{r: &bs}
|
bs.new[o] = []byte(new)
|
||||||
}
|
}
|
||||||
return &Replacer{r: &gen}
|
return &Replacer{r: bs}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace returns a copy of s with all replacements performed.
|
// Replace returns a copy of s with all replacements performed.
|
||||||
@ -94,6 +88,16 @@ type genericReplacer struct {
|
|||||||
|
|
||||||
type pair struct{ old, new string }
|
type pair struct{ old, new string }
|
||||||
|
|
||||||
|
func makeGenericReplacer(oldnew []string) *genericReplacer {
|
||||||
|
gen := &genericReplacer{
|
||||||
|
p: make([]pair, len(oldnew)/2),
|
||||||
|
}
|
||||||
|
for i := 0; i < len(oldnew); i += 2 {
|
||||||
|
gen.p[i/2] = pair{oldnew[i], oldnew[i+1]}
|
||||||
|
}
|
||||||
|
return gen
|
||||||
|
}
|
||||||
|
|
||||||
type appendSliceWriter struct {
|
type appendSliceWriter struct {
|
||||||
b []byte
|
b []byte
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func TestReplacer(t *testing.T) {
|
|||||||
testCase{inc, "\x00\xff", "\x01\x00"},
|
testCase{inc, "\x00\xff", "\x01\x00"},
|
||||||
testCase{inc, "", ""},
|
testCase{inc, "", ""},
|
||||||
|
|
||||||
testCase{NewReplacer("a", "1", "a", "2"), "brad", "br2d"}, // TODO: should this be "br1d"?
|
testCase{NewReplacer("a", "1", "a", "2"), "brad", "br1d"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// repeat maps "a"->"a", "b"->"bb", "c"->"ccc", ...
|
// repeat maps "a"->"a", "b"->"bb", "c"->"ccc", ...
|
||||||
@ -95,7 +95,7 @@ func TestReplacer(t *testing.T) {
|
|||||||
testCase{repeat, "abba", "abbbba"},
|
testCase{repeat, "abba", "abbbba"},
|
||||||
testCase{repeat, "", ""},
|
testCase{repeat, "", ""},
|
||||||
|
|
||||||
testCase{NewReplacer("a", "11", "a", "22"), "brad", "br22d"}, // TODO: should this be "br11d"?
|
testCase{NewReplacer("a", "11", "a", "22"), "brad", "br11d"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// The remaining test cases have variable length old strings.
|
// The remaining test cases have variable length old strings.
|
||||||
@ -246,6 +246,14 @@ func TestReplacer(t *testing.T) {
|
|||||||
testCase{blankFoo, "", "X"},
|
testCase{blankFoo, "", "X"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// No-arg test cases.
|
||||||
|
|
||||||
|
nop := NewReplacer()
|
||||||
|
testCases = append(testCases,
|
||||||
|
testCase{nop, "abc", "abc"},
|
||||||
|
testCase{nop, "", ""},
|
||||||
|
)
|
||||||
|
|
||||||
// Run the test cases.
|
// Run the test cases.
|
||||||
|
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
@ -277,9 +285,10 @@ func TestPickAlgorithm(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{capitalLetters, "*strings.byteReplacer"},
|
{capitalLetters, "*strings.byteReplacer"},
|
||||||
|
{htmlEscaper, "*strings.byteStringReplacer"},
|
||||||
{NewReplacer("12", "123"), "*strings.genericReplacer"},
|
{NewReplacer("12", "123"), "*strings.genericReplacer"},
|
||||||
{NewReplacer("1", "12"), "*strings.byteStringReplacer"},
|
{NewReplacer("1", "12"), "*strings.byteStringReplacer"},
|
||||||
{htmlEscaper, "*strings.byteStringReplacer"},
|
{NewReplacer("a", "1", "b", "12", "cde", "123"), "*strings.genericReplacer"},
|
||||||
}
|
}
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
got := fmt.Sprintf("%T", tc.r.Replacer())
|
got := fmt.Sprintf("%T", tc.r.Replacer())
|
||||||
|
Loading…
Reference in New Issue
Block a user