[perf] Optimize AWK-style tokenizer for --nth
Approx. 50% less memory footprint and 40% improvement in query time
This commit is contained in:
parent
1d4057c209
commit
ddc7bb9064
@ -97,10 +97,11 @@ const (
|
|||||||
func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
||||||
// 9, 32
|
// 9, 32
|
||||||
ret := []util.Chars{}
|
ret := []util.Chars{}
|
||||||
str := []rune{}
|
|
||||||
prefixLength := 0
|
prefixLength := 0
|
||||||
state := awkNil
|
state := awkNil
|
||||||
numChars := input.Length()
|
numChars := input.Length()
|
||||||
|
begin := 0
|
||||||
|
end := 0
|
||||||
for idx := 0; idx < numChars; idx++ {
|
for idx := 0; idx < numChars; idx++ {
|
||||||
r := input.Get(idx)
|
r := input.Get(idx)
|
||||||
white := r == 9 || r == 32
|
white := r == 9 || r == 32
|
||||||
@ -109,26 +110,24 @@ func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
|||||||
if white {
|
if white {
|
||||||
prefixLength++
|
prefixLength++
|
||||||
} else {
|
} else {
|
||||||
state = awkBlack
|
state, begin, end = awkBlack, idx, idx+1
|
||||||
str = append(str, r)
|
|
||||||
}
|
}
|
||||||
case awkBlack:
|
case awkBlack:
|
||||||
str = append(str, r)
|
end = idx + 1
|
||||||
if white {
|
if white {
|
||||||
state = awkWhite
|
state = awkWhite
|
||||||
}
|
}
|
||||||
case awkWhite:
|
case awkWhite:
|
||||||
if white {
|
if white {
|
||||||
str = append(str, r)
|
end = idx + 1
|
||||||
} else {
|
} else {
|
||||||
ret = append(ret, util.RunesToChars(str))
|
ret = append(ret, input.Slice(begin, end))
|
||||||
state = awkBlack
|
state, begin, end = awkBlack, idx, idx+1
|
||||||
str = []rune{r}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(str) > 0 {
|
if begin < end {
|
||||||
ret = append(ret, util.RunesToChars(str))
|
ret = append(ret, input.Slice(begin, end))
|
||||||
}
|
}
|
||||||
return ret, prefixLength
|
return ret, prefixLength
|
||||||
}
|
}
|
||||||
@ -187,19 +186,19 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
transTokens := make([]Token, len(withNth))
|
transTokens := make([]Token, len(withNth))
|
||||||
numTokens := len(tokens)
|
numTokens := len(tokens)
|
||||||
for idx, r := range withNth {
|
for idx, r := range withNth {
|
||||||
part := []rune{}
|
parts := []util.Chars{}
|
||||||
minIdx := 0
|
minIdx := 0
|
||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
part = append(part, joinTokensAsRunes(tokens)...)
|
parts = append(parts, util.RunesToChars(joinTokensAsRunes(tokens)))
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
idx += numTokens + 1
|
idx += numTokens + 1
|
||||||
}
|
}
|
||||||
if idx >= 1 && idx <= numTokens {
|
if idx >= 1 && idx <= numTokens {
|
||||||
minIdx = idx - 1
|
minIdx = idx - 1
|
||||||
part = append(part, tokens[idx-1].text.ToRunes()...)
|
parts = append(parts, tokens[idx-1].text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -226,17 +225,32 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
minIdx = util.Max(0, begin-1)
|
minIdx = util.Max(0, begin-1)
|
||||||
for idx := begin; idx <= end; idx++ {
|
for idx := begin; idx <= end; idx++ {
|
||||||
if idx >= 1 && idx <= numTokens {
|
if idx >= 1 && idx <= numTokens {
|
||||||
part = append(part, tokens[idx-1].text.ToRunes()...)
|
parts = append(parts, tokens[idx-1].text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Merge multiple parts
|
||||||
|
var merged util.Chars
|
||||||
|
switch len(parts) {
|
||||||
|
case 0:
|
||||||
|
merged = util.RunesToChars([]rune{})
|
||||||
|
case 1:
|
||||||
|
merged = parts[0]
|
||||||
|
default:
|
||||||
|
runes := []rune{}
|
||||||
|
for _, part := range parts {
|
||||||
|
runes = append(runes, part.ToRunes()...)
|
||||||
|
}
|
||||||
|
merged = util.RunesToChars(runes)
|
||||||
|
}
|
||||||
|
|
||||||
var prefixLength int
|
var prefixLength int
|
||||||
if minIdx < numTokens {
|
if minIdx < numTokens {
|
||||||
prefixLength = tokens[minIdx].prefixLength
|
prefixLength = tokens[minIdx].prefixLength
|
||||||
} else {
|
} else {
|
||||||
prefixLength = 0
|
prefixLength = 0
|
||||||
}
|
}
|
||||||
transTokens[idx] = Token{util.RunesToChars(part), prefixLength, util.TrimLen(part)}
|
transTokens[idx] = Token{merged, prefixLength, merged.TrimLength()}
|
||||||
}
|
}
|
||||||
return transTokens
|
return transTokens
|
||||||
}
|
}
|
||||||
|
@ -111,3 +111,10 @@ func (chars *Chars) ToRunes() []rune {
|
|||||||
}
|
}
|
||||||
return runes
|
return runes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Slice(b int, e int) Chars {
|
||||||
|
if chars.runes != nil {
|
||||||
|
return Chars{runes: chars.runes[b:e]}
|
||||||
|
}
|
||||||
|
return Chars{bytes: chars.bytes[b:e]}
|
||||||
|
}
|
||||||
|
@ -34,3 +34,24 @@ func TestCharsToString(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTrimLength(t *testing.T) {
|
||||||
|
check := func(str string, exp int) {
|
||||||
|
chars := ToChars([]byte(str))
|
||||||
|
trimmed := chars.TrimLength()
|
||||||
|
if trimmed != exp {
|
||||||
|
t.Errorf("Invalid TrimLength result for '%s': %d (expected %d)",
|
||||||
|
str, trimmed, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("hello", 5)
|
||||||
|
check("hello ", 5)
|
||||||
|
check("hello ", 5)
|
||||||
|
check(" hello", 5)
|
||||||
|
check(" hello", 5)
|
||||||
|
check(" hello ", 5)
|
||||||
|
check(" hello ", 5)
|
||||||
|
check("h o", 5)
|
||||||
|
check(" h o ", 5)
|
||||||
|
check(" ", 0)
|
||||||
|
}
|
||||||
|
@ -83,30 +83,6 @@ func IsTty() bool {
|
|||||||
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0
|
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLen returns the length of trimmed rune array
|
|
||||||
func TrimLen(runes []rune) int {
|
|
||||||
var i int
|
|
||||||
for i = len(runes) - 1; i >= 0; i-- {
|
|
||||||
char := runes[i]
|
|
||||||
if char != ' ' && char != '\t' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Completely empty
|
|
||||||
if i < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var j int
|
|
||||||
for j = 0; j < len(runes); j++ {
|
|
||||||
char := runes[j]
|
|
||||||
if char != ' ' && char != '\t' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i - j + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
shell := os.Getenv("SHELL")
|
shell := os.Getenv("SHELL")
|
||||||
|
@ -20,23 +20,3 @@ func TestContrain(t *testing.T) {
|
|||||||
t.Error("Expected", 3)
|
t.Error("Expected", 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrimLen(t *testing.T) {
|
|
||||||
check := func(str string, exp int) {
|
|
||||||
trimmed := TrimLen([]rune(str))
|
|
||||||
if trimmed != exp {
|
|
||||||
t.Errorf("Invalid TrimLen result for '%s': %d (expected %d)",
|
|
||||||
str, trimmed, exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check("hello", 5)
|
|
||||||
check("hello ", 5)
|
|
||||||
check("hello ", 5)
|
|
||||||
check(" hello", 5)
|
|
||||||
check(" hello", 5)
|
|
||||||
check(" hello ", 5)
|
|
||||||
check(" hello ", 5)
|
|
||||||
check("h o", 5)
|
|
||||||
check(" h o ", 5)
|
|
||||||
check(" ", 0)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user