mirror of
https://github.com/ii64/sonic.git
synced 2026-06-22 17:36:48 +08:00
feat:(ast) support sorting node keys (#164)
* feat: ast.Node support ForEach() iteration (DFS) Change-Id: Ia53f1db2814036e12b760dfbb7a21094a6abd541 * feat: support Node's key sorting Change-Id: I0b93d9b4feada853fa2ca9f48277da71948a95be * fmt Change-Id: I1a53170959c08f1a32f02b0f163207254a87362c * test: forbid `checkptr` Change-Id: I6b34f74ee3bad883f515728300bf735a9e10b0d6 * fmt: add comments Co-authored-by: duanyi.aster <duanyi.aster@bytedance.com>
This commit is contained in:
parent
8dfaa13d3e
commit
c3cb5de704
10 changed files with 679 additions and 276 deletions
2
.github/workflows/push-check-go115.yml
vendored
2
.github/workflows/push-check-go115.yml
vendored
|
|
@ -21,4 +21,4 @@ jobs:
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: GOMAXPROCS=4 go test -v -covermode=atomic -coverprofile=coverage.out ./...
|
run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -covermode=atomic -coverprofile=coverage.out ./...
|
||||||
|
|
|
||||||
2
.github/workflows/push-check-go116.yml
vendored
2
.github/workflows/push-check-go116.yml
vendored
|
|
@ -21,4 +21,4 @@ jobs:
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: GOMAXPROCS=4 go test -v -race -covermode=atomic -coverprofile=coverage.out ./...
|
run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race -covermode=atomic -coverprofile=coverage.out ./...
|
||||||
|
|
|
||||||
2
.github/workflows/push-check-go117.yml
vendored
2
.github/workflows/push-check-go117.yml
vendored
|
|
@ -21,4 +21,4 @@ jobs:
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: GOMAXPROCS=4 go test -v -race -covermode=atomic -coverprofile=coverage.out ./...
|
run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race -covermode=atomic -coverprofile=coverage.out ./...
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`fmt`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -97,3 +99,62 @@ func (self *ObjectIterator) Next(p *Pair) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sequence represents scanning path of single-layer nodes.
|
||||||
|
// Index indicates the value's order in both V_ARRAY and V_OBJECT json.
|
||||||
|
// Key is the value's key (for V_OBJECT json only, otherwise it will be nil).
|
||||||
|
type Sequence struct {
|
||||||
|
Index int
|
||||||
|
Key *string
|
||||||
|
// Level int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is string representation of one Sequence
|
||||||
|
func (s Sequence) String() string {
|
||||||
|
k := ""
|
||||||
|
if s.Key != nil {
|
||||||
|
k = *s.Key
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Sequence(%d, %q)", s.Index, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scanner func(path Sequence, node *Node) bool
|
||||||
|
|
||||||
|
// ForEach scans one V_OBJECT node's children from JSON head to tail,
|
||||||
|
// and pass the Sequence and Node of corresponding JSON value.
|
||||||
|
//
|
||||||
|
// Especailly, if the node is not V_ARRAY or V_OBJECT,
|
||||||
|
// the node itself will be returned and Sequence.Index == -1.
|
||||||
|
func (self *Node) ForEach(sc Scanner) error {
|
||||||
|
switch self.itype() {
|
||||||
|
case types.V_ARRAY:
|
||||||
|
ns, err := self.UnsafeArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range ns {
|
||||||
|
if !sc(Sequence{i, nil}, &ns[i]) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case types.V_OBJECT:
|
||||||
|
ns, err := self.UnsafeMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range ns {
|
||||||
|
if !sc(Sequence{i, &ns[i].Key}, &ns[i].Value) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
sc(Sequence{-1, nil}, self)
|
||||||
|
}
|
||||||
|
return self.Check()
|
||||||
|
}
|
||||||
|
|
||||||
|
type PairSlice []Pair
|
||||||
|
|
||||||
|
func (self PairSlice) Sort() {
|
||||||
|
radixQsort(self, 0, maxDepth(len(self)))
|
||||||
|
}
|
||||||
|
|
@ -20,13 +20,14 @@ import (
|
||||||
`fmt`
|
`fmt`
|
||||||
`strconv`
|
`strconv`
|
||||||
`testing`
|
`testing`
|
||||||
|
|
||||||
|
`github.com/stretchr/testify/assert`
|
||||||
)
|
)
|
||||||
|
|
||||||
func getTestIteratorSample() (string, int) {
|
func getTestIteratorSample(loop int) (string, int) {
|
||||||
var data []int
|
var data []int
|
||||||
var v1 = ""
|
var v1 = ""
|
||||||
var v2 = ""
|
var v2 = ""
|
||||||
loop := _DEFAULT_NODE_CAP+1
|
|
||||||
for i:=0;i<loop;i++{
|
for i:=0;i<loop;i++{
|
||||||
data = append(data, i*i)
|
data = append(data, i*i)
|
||||||
v1 += strconv.Itoa(i)
|
v1 += strconv.Itoa(i)
|
||||||
|
|
@ -39,8 +40,57 @@ func getTestIteratorSample() (string, int) {
|
||||||
return `{"array":[`+v1+`], "object":{`+v2+`}}`, loop
|
return `{"array":[`+v1+`], "object":{`+v2+`}}`, loop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestForEach(t *testing.T) {
|
||||||
|
pathes := []Sequence{}
|
||||||
|
values := []*Node{}
|
||||||
|
sc := func(path Sequence, node *Node) bool {
|
||||||
|
pathes = append(pathes, path)
|
||||||
|
values = append(values, node)
|
||||||
|
if path.Key != nil && *path.Key == "array" {
|
||||||
|
node.ForEach(func(path Sequence, node *Node)bool{
|
||||||
|
pathes = append(pathes, path)
|
||||||
|
values = append(values, node)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
str, _ := getTestIteratorSample(3)
|
||||||
|
fmt.Println(str)
|
||||||
|
root, err := NewSearcher(str).GetByPath()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = root.ForEach(sc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
eObjKey := "object"
|
||||||
|
eArrKey := "array"
|
||||||
|
expPath := []Sequence{
|
||||||
|
{0, &eArrKey},
|
||||||
|
{0, nil},
|
||||||
|
{1, nil},
|
||||||
|
{2, nil},
|
||||||
|
{1, &eObjKey},
|
||||||
|
}
|
||||||
|
expValue := []*Node{
|
||||||
|
root.Get("array"),
|
||||||
|
root.GetByPath("array", 0),
|
||||||
|
root.GetByPath("array", 1),
|
||||||
|
root.GetByPath("array", 2),
|
||||||
|
root.Get("object"),
|
||||||
|
}
|
||||||
|
// fmt.Printf("pathes:%+v\n", pathes)
|
||||||
|
// fmt.Printf("values:%+v\n", values)
|
||||||
|
assert.Equal(t, expPath, pathes)
|
||||||
|
assert.Equal(t, expValue, values)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestRawIterator(t *testing.T) {
|
func TestRawIterator(t *testing.T) {
|
||||||
str, loop := getTestIteratorSample()
|
str, loop := getTestIteratorSample(_DEFAULT_NODE_CAP)
|
||||||
fmt.Println(str)
|
fmt.Println(str)
|
||||||
|
|
||||||
root, err := NewSearcher(str).GetByPath("array")
|
root, err := NewSearcher(str).GetByPath("array")
|
||||||
|
|
@ -94,7 +144,7 @@ func TestRawIterator(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIterator(t *testing.T) {
|
func TestIterator(t *testing.T) {
|
||||||
str, loop := getTestIteratorSample()
|
str, loop := getTestIteratorSample(_DEFAULT_NODE_CAP)
|
||||||
fmt.Println(str)
|
fmt.Println(str)
|
||||||
|
|
||||||
root, err := NewParser(str).Parse()
|
root, err := NewParser(str).Parse()
|
||||||
|
|
|
||||||
182
ast/node.go
182
ast/node.go
|
|
@ -24,7 +24,6 @@ import (
|
||||||
`github.com/bytedance/sonic/decoder`
|
`github.com/bytedance/sonic/decoder`
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
`github.com/bytedance/sonic/unquote`
|
|
||||||
`github.com/chenzhuoyu/base64x`
|
`github.com/chenzhuoyu/base64x`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -621,6 +620,34 @@ func (self *Node) UnsafeMap() ([]Pair, error) {
|
||||||
return *(*[]Pair)(s), nil
|
return *(*[]Pair)(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortKeys sorts children of a V_OBJECT node in ascending key-order.
|
||||||
|
// If recurse is true, it recursively sorts children's children as long as a V_OBJECT node is found.
|
||||||
|
func (self *Node) SortKeys(recurse bool) (err error) {
|
||||||
|
ps, err := self.UnsafeMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
PairSlice(ps).Sort()
|
||||||
|
if recurse {
|
||||||
|
var sc Scanner
|
||||||
|
sc = func(path Sequence, node *Node) bool {
|
||||||
|
if node.itype() == types.V_OBJECT {
|
||||||
|
if err := node.SortKeys(recurse); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.itype() == types.V_ARRAY {
|
||||||
|
if err := node.ForEach(sc); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
self.ForEach(sc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Array loads all indexes of an array node
|
// Array loads all indexes of an array node
|
||||||
func (self *Node) Array() ([]interface{}, error) {
|
func (self *Node) Array() ([]interface{}, error) {
|
||||||
if self.isAny() {
|
if self.isAny() {
|
||||||
|
|
@ -673,7 +700,7 @@ func (self *Node) ArrayUseNode() ([]Node, error) {
|
||||||
if err := self.should(types.V_ARRAY, "an array"); err != nil {
|
if err := self.should(types.V_ARRAY, "an array"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := self.loadAllIndex(); err != nil {
|
if err := self.skipAllIndex(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return self.toGenericArrayUseNode()
|
return self.toGenericArrayUseNode()
|
||||||
|
|
@ -685,7 +712,7 @@ func (self *Node) UnsafeArray() ([]Node, error) {
|
||||||
if err := self.should(types.V_ARRAY, "an array"); err != nil {
|
if err := self.should(types.V_ARRAY, "an array"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := self.loadAllIndex(); err != nil {
|
if err := self.skipAllIndex(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s := ptr2slice(self.p, self.len(), self.cap())
|
s := ptr2slice(self.p, self.len(), self.cap())
|
||||||
|
|
@ -806,7 +833,9 @@ func (self *Node) LoadAll() error {
|
||||||
}
|
}
|
||||||
for i := 0; i < e; i++ {
|
for i := 0; i < e; i++ {
|
||||||
n := self.nodeAt(i)
|
n := self.nodeAt(i)
|
||||||
n.parseRaw(true)
|
if n.IsRaw() {
|
||||||
|
n.parseRaw(true)
|
||||||
|
}
|
||||||
if err := n.Check(); err != nil {
|
if err := n.Check(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -819,7 +848,9 @@ func (self *Node) LoadAll() error {
|
||||||
}
|
}
|
||||||
for i := 0; i < e; i++ {
|
for i := 0; i < e; i++ {
|
||||||
n := self.pairAt(i)
|
n := self.pairAt(i)
|
||||||
n.Value.parseRaw(true)
|
if n.Value.IsRaw() {
|
||||||
|
n.Value.parseRaw(true)
|
||||||
|
}
|
||||||
if err := n.Value.Check(); err != nil {
|
if err := n.Value.Check(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -957,147 +988,6 @@ func (self *Node) skipAllKey() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Node) skipNextNode() *Node {
|
|
||||||
if !self.isLazy() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser, stack := self.getParserAndArrayStack()
|
|
||||||
ret := stack.v
|
|
||||||
sp := parser.p
|
|
||||||
ns := len(parser.s)
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
if parser.p = parser.lspace(sp); parser.p >= ns {
|
|
||||||
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for empty array */
|
|
||||||
if parser.s[parser.p] == ']' {
|
|
||||||
parser.p++
|
|
||||||
self.setArray(ret)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var val Node
|
|
||||||
/* skip the value */
|
|
||||||
if start, err := parser.skip(); err != 0 {
|
|
||||||
return newSyntaxError(parser.syntaxError(err))
|
|
||||||
} else {
|
|
||||||
t := switchRawType(parser.s[start])
|
|
||||||
if t == _V_NONE {
|
|
||||||
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
|
||||||
}
|
|
||||||
val = newRawNode(parser.s[start:parser.p], t)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* add the value to result */
|
|
||||||
ret = append(ret, val)
|
|
||||||
parser.p = parser.lspace(parser.p)
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
if parser.p >= ns {
|
|
||||||
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for the next character */
|
|
||||||
switch parser.s[parser.p] {
|
|
||||||
case ',':
|
|
||||||
parser.p++
|
|
||||||
self.setLazyArray(parser, ret)
|
|
||||||
return &ret[len(ret)-1]
|
|
||||||
case ']':
|
|
||||||
parser.p++
|
|
||||||
self.setArray(ret)
|
|
||||||
return &ret[len(ret)-1]
|
|
||||||
default:
|
|
||||||
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Node) skipNextPair() (*Pair) {
|
|
||||||
if !self.isLazy() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser, stack := self.getParserAndObjectStack()
|
|
||||||
ret := stack.v
|
|
||||||
sp := parser.p
|
|
||||||
ns := len(parser.s)
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
if parser.p = parser.lspace(sp); parser.p >= ns {
|
|
||||||
return &Pair{"", *newSyntaxError(parser.syntaxError(types.ERR_EOF))}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for empty object */
|
|
||||||
if parser.s[parser.p] == '}' {
|
|
||||||
parser.p++
|
|
||||||
self.setObject(ret)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/* decode one pair */
|
|
||||||
var val Node
|
|
||||||
var njs types.JsonState
|
|
||||||
var err types.ParsingError
|
|
||||||
|
|
||||||
/* decode the key */
|
|
||||||
if njs = parser.decodeValue(); njs.Vt != types.V_STRING {
|
|
||||||
return &Pair{"", *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* extract the key */
|
|
||||||
idx := parser.p - 1
|
|
||||||
key := parser.s[njs.Iv:idx]
|
|
||||||
|
|
||||||
/* check for escape sequence */
|
|
||||||
if njs.Ep != -1 {
|
|
||||||
if key, err = unquote.String(key); err != 0 {
|
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(err))}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expect a ':' delimiter */
|
|
||||||
if err = parser.delim(); err != 0 {
|
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(err))}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* skip the value */
|
|
||||||
if start, err := parser.skip(); err != 0 {
|
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(err))}
|
|
||||||
} else {
|
|
||||||
t := switchRawType(parser.s[start])
|
|
||||||
if t == _V_NONE {
|
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
|
||||||
}
|
|
||||||
val = newRawNode(parser.s[start:parser.p], t)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* add the value to result */
|
|
||||||
ret = append(ret, Pair{Key: key, Value: val})
|
|
||||||
parser.p = parser.lspace(parser.p)
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
if parser.p >= ns {
|
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_EOF))}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for the next character */
|
|
||||||
switch parser.s[parser.p] {
|
|
||||||
case ',':
|
|
||||||
parser.p++
|
|
||||||
self.setLazyObject(parser, ret)
|
|
||||||
return &ret[len(ret)-1]
|
|
||||||
case '}':
|
|
||||||
parser.p++
|
|
||||||
self.setObject(ret)
|
|
||||||
return &ret[len(ret)-1]
|
|
||||||
default:
|
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Node) skipKey(key string) (*Node, int) {
|
func (self *Node) skipKey(key string) (*Node, int) {
|
||||||
nb := self.len()
|
nb := self.len()
|
||||||
lazy := self.isLazy()
|
lazy := self.isLazy()
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,66 @@ import (
|
||||||
`strconv`
|
`strconv`
|
||||||
`testing`
|
`testing`
|
||||||
|
|
||||||
|
`github.com/bytedance/sonic/encoder`
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
`github.com/stretchr/testify/assert`
|
`github.com/stretchr/testify/assert`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNodeSortKeys(t *testing.T) {
|
||||||
|
root, err := NewSearcher(_TwitterJson).GetByPath()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
obj, err := root.MapUseNumber()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp, err := encoder.Encode(obj, encoder.SortMapKeys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := root.SortKeys(true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
act, err := root.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, len(exp), len(act))
|
||||||
|
assert.Equal(t, string(exp), string(act))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNodeSortKeys(b *testing.B) {
|
||||||
|
root, err := NewSearcher(_TwitterJson).GetByPath()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := root.LoadAll(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run("single", func(b *testing.B) {
|
||||||
|
r := root.Get("statuses")
|
||||||
|
if r.Check() != nil {
|
||||||
|
b.Fatal(r.Error())
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(_TwitterJson)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i:=0; i<b.N; i++ {
|
||||||
|
_ = root.SortKeys(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("recurse", func(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(_TwitterJson)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i:=0; i<b.N; i++ {
|
||||||
|
_ = root.SortKeys(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//go:noinline
|
//go:noinline
|
||||||
func stackObj() interface{} {
|
func stackObj() interface{} {
|
||||||
var a int = 1
|
var a int = 1
|
||||||
|
|
@ -451,7 +506,7 @@ func TestUnset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnsafeNode(t *testing.T) {
|
func TestUnsafeNode(t *testing.T) {
|
||||||
str, loop := getTestIteratorSample()
|
str, loop := getTestIteratorSample(_DEFAULT_NODE_CAP)
|
||||||
|
|
||||||
root, err := NewSearcher(str).GetByPath("array")
|
root, err := NewSearcher(str).GetByPath("array")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -490,7 +545,7 @@ func TestUnsafeNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUseNode(t *testing.T) {
|
func TestUseNode(t *testing.T) {
|
||||||
str, loop := getTestIteratorSample()
|
str, loop := getTestIteratorSample(_DEFAULT_NODE_CAP)
|
||||||
root, e := NewParser(str).Parse()
|
root, e := NewParser(str).Parse()
|
||||||
if e != 0 {
|
if e != 0 {
|
||||||
t.Fatal(e)
|
t.Fatal(e)
|
||||||
|
|
@ -576,7 +631,7 @@ func TestUseNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUseNumber(t *testing.T) {
|
func TestUseNumber(t *testing.T) {
|
||||||
str, _ := getTestIteratorSample()
|
str, _ := getTestIteratorSample(_DEFAULT_NODE_CAP)
|
||||||
root, e := NewParser(str).Parse()
|
root, e := NewParser(str).Parse()
|
||||||
if e != 0 {
|
if e != 0 {
|
||||||
t.Fatal(e)
|
t.Fatal(e)
|
||||||
|
|
|
||||||
260
ast/parser.go
260
ast/parser.go
|
|
@ -335,6 +335,266 @@ func (self *Parser) skip() (int, types.ParsingError) {
|
||||||
return start, 0
|
return start, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Parser) searchKey(match string) types.ParsingError {
|
||||||
|
ns := len(self.s)
|
||||||
|
if err := self.object(); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
if self.p = self.lspace(self.p); self.p >= ns {
|
||||||
|
return types.ERR_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for empty object */
|
||||||
|
if self.s[self.p] == '}' {
|
||||||
|
self.p++
|
||||||
|
return _ERR_NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
|
var njs types.JsonState
|
||||||
|
var err types.ParsingError
|
||||||
|
/* decode each pair */
|
||||||
|
for {
|
||||||
|
|
||||||
|
/* decode the key */
|
||||||
|
if njs = self.decodeValue(); njs.Vt != types.V_STRING {
|
||||||
|
return types.ERR_INVALID_CHAR
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extract the key */
|
||||||
|
idx := self.p - 1
|
||||||
|
key := self.s[njs.Iv:idx]
|
||||||
|
|
||||||
|
/* check for escape sequence */
|
||||||
|
if njs.Ep != -1 {
|
||||||
|
if key, err = unquote.String(key); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expect a ':' delimiter */
|
||||||
|
if err = self.delim(); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* skip value */
|
||||||
|
if key != match {
|
||||||
|
if _, err = self.skip(); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
self.p = self.lspace(self.p)
|
||||||
|
if self.p >= ns {
|
||||||
|
return types.ERR_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for the next character */
|
||||||
|
switch self.s[self.p] {
|
||||||
|
case ',':
|
||||||
|
self.p++
|
||||||
|
case '}':
|
||||||
|
self.p++
|
||||||
|
return _ERR_NOT_FOUND
|
||||||
|
default:
|
||||||
|
return types.ERR_INVALID_CHAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Parser) searchIndex(idx int) types.ParsingError {
|
||||||
|
ns := len(self.s)
|
||||||
|
if err := self.array(); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
if self.p = self.lspace(self.p); self.p >= ns {
|
||||||
|
return types.ERR_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for empty array */
|
||||||
|
if self.s[self.p] == ']' {
|
||||||
|
self.p++
|
||||||
|
return _ERR_NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
|
var err types.ParsingError
|
||||||
|
/* allocate array space and parse every element */
|
||||||
|
for i := 0; i < idx; i++ {
|
||||||
|
|
||||||
|
/* decode the value */
|
||||||
|
if _, err = self.skip(); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
self.p = self.lspace(self.p)
|
||||||
|
if self.p >= ns {
|
||||||
|
return types.ERR_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for the next character */
|
||||||
|
switch self.s[self.p] {
|
||||||
|
case ',':
|
||||||
|
self.p++
|
||||||
|
case ']':
|
||||||
|
self.p++
|
||||||
|
return _ERR_NOT_FOUND
|
||||||
|
default:
|
||||||
|
return types.ERR_INVALID_CHAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node) skipNextNode() *Node {
|
||||||
|
if !self.isLazy() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parser, stack := self.getParserAndArrayStack()
|
||||||
|
ret := stack.v
|
||||||
|
sp := parser.p
|
||||||
|
ns := len(parser.s)
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
if parser.p = parser.lspace(sp); parser.p >= ns {
|
||||||
|
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for empty array */
|
||||||
|
if parser.s[parser.p] == ']' {
|
||||||
|
parser.p++
|
||||||
|
self.setArray(ret)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var val Node
|
||||||
|
/* skip the value */
|
||||||
|
if start, err := parser.skip(); err != 0 {
|
||||||
|
return newSyntaxError(parser.syntaxError(err))
|
||||||
|
} else {
|
||||||
|
t := switchRawType(parser.s[start])
|
||||||
|
if t == _V_NONE {
|
||||||
|
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||||
|
}
|
||||||
|
val = newRawNode(parser.s[start:parser.p], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add the value to result */
|
||||||
|
ret = append(ret, val)
|
||||||
|
parser.p = parser.lspace(parser.p)
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
if parser.p >= ns {
|
||||||
|
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for the next character */
|
||||||
|
switch parser.s[parser.p] {
|
||||||
|
case ',':
|
||||||
|
parser.p++
|
||||||
|
self.setLazyArray(parser, ret)
|
||||||
|
return &ret[len(ret)-1]
|
||||||
|
case ']':
|
||||||
|
parser.p++
|
||||||
|
self.setArray(ret)
|
||||||
|
return &ret[len(ret)-1]
|
||||||
|
default:
|
||||||
|
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node) skipNextPair() (*Pair) {
|
||||||
|
if !self.isLazy() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parser, stack := self.getParserAndObjectStack()
|
||||||
|
ret := stack.v
|
||||||
|
sp := parser.p
|
||||||
|
ns := len(parser.s)
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
if parser.p = parser.lspace(sp); parser.p >= ns {
|
||||||
|
return &Pair{"", *newSyntaxError(parser.syntaxError(types.ERR_EOF))}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for empty object */
|
||||||
|
if parser.s[parser.p] == '}' {
|
||||||
|
parser.p++
|
||||||
|
self.setObject(ret)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* decode one pair */
|
||||||
|
var val Node
|
||||||
|
var njs types.JsonState
|
||||||
|
var err types.ParsingError
|
||||||
|
|
||||||
|
/* decode the key */
|
||||||
|
if njs = parser.decodeValue(); njs.Vt != types.V_STRING {
|
||||||
|
return &Pair{"", *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extract the key */
|
||||||
|
idx := parser.p - 1
|
||||||
|
key := parser.s[njs.Iv:idx]
|
||||||
|
|
||||||
|
/* check for escape sequence */
|
||||||
|
if njs.Ep != -1 {
|
||||||
|
if key, err = unquote.String(key); err != 0 {
|
||||||
|
return &Pair{key, *newSyntaxError(parser.syntaxError(err))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expect a ':' delimiter */
|
||||||
|
if err = parser.delim(); err != 0 {
|
||||||
|
return &Pair{key, *newSyntaxError(parser.syntaxError(err))}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* skip the value */
|
||||||
|
if start, err := parser.skip(); err != 0 {
|
||||||
|
return &Pair{key, *newSyntaxError(parser.syntaxError(err))}
|
||||||
|
} else {
|
||||||
|
t := switchRawType(parser.s[start])
|
||||||
|
if t == _V_NONE {
|
||||||
|
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
||||||
|
}
|
||||||
|
val = newRawNode(parser.s[start:parser.p], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add the value to result */
|
||||||
|
ret = append(ret, Pair{Key: key, Value: val})
|
||||||
|
parser.p = parser.lspace(parser.p)
|
||||||
|
|
||||||
|
/* check for EOF */
|
||||||
|
if parser.p >= ns {
|
||||||
|
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_EOF))}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for the next character */
|
||||||
|
switch parser.s[parser.p] {
|
||||||
|
case ',':
|
||||||
|
parser.p++
|
||||||
|
self.setLazyObject(parser, ret)
|
||||||
|
return &ret[len(ret)-1]
|
||||||
|
case '}':
|
||||||
|
parser.p++
|
||||||
|
self.setObject(ret)
|
||||||
|
return &ret[len(ret)-1]
|
||||||
|
default:
|
||||||
|
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Parser Factory **/
|
/** Parser Factory **/
|
||||||
|
|
||||||
// Loads parse all json into interface{}
|
// Loads parse all json into interface{}
|
||||||
|
|
|
||||||
121
ast/search.go
121
ast/search.go
|
|
@ -20,7 +20,6 @@ import (
|
||||||
`fmt`
|
`fmt`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/unquote`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Searcher struct {
|
type Searcher struct {
|
||||||
|
|
@ -70,122 +69,4 @@ func (self *Searcher) GetByPath(path ...interface{}) (Node, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRawNode(self.parser.s[start:self.parser.p], t), nil
|
return newRawNode(self.parser.s[start:self.parser.p], t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Parser) searchKey(match string) types.ParsingError {
|
|
||||||
ns := len(self.s)
|
|
||||||
if err := self.object(); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
if self.p = self.lspace(self.p); self.p >= ns {
|
|
||||||
return types.ERR_EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for empty object */
|
|
||||||
if self.s[self.p] == '}' {
|
|
||||||
self.p++
|
|
||||||
return _ERR_NOT_FOUND
|
|
||||||
}
|
|
||||||
|
|
||||||
var njs types.JsonState
|
|
||||||
var err types.ParsingError
|
|
||||||
/* decode each pair */
|
|
||||||
for {
|
|
||||||
|
|
||||||
/* decode the key */
|
|
||||||
if njs = self.decodeValue(); njs.Vt != types.V_STRING {
|
|
||||||
return types.ERR_INVALID_CHAR
|
|
||||||
}
|
|
||||||
|
|
||||||
/* extract the key */
|
|
||||||
idx := self.p - 1
|
|
||||||
key := self.s[njs.Iv:idx]
|
|
||||||
|
|
||||||
/* check for escape sequence */
|
|
||||||
if njs.Ep != -1 {
|
|
||||||
if key, err = unquote.String(key); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expect a ':' delimiter */
|
|
||||||
if err = self.delim(); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* skip value */
|
|
||||||
if key != match {
|
|
||||||
if _, err = self.skip(); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
self.p = self.lspace(self.p)
|
|
||||||
if self.p >= ns {
|
|
||||||
return types.ERR_EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for the next character */
|
|
||||||
switch self.s[self.p] {
|
|
||||||
case ',':
|
|
||||||
self.p++
|
|
||||||
case '}':
|
|
||||||
self.p++
|
|
||||||
return _ERR_NOT_FOUND
|
|
||||||
default:
|
|
||||||
return types.ERR_INVALID_CHAR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Parser) searchIndex(idx int) types.ParsingError {
|
|
||||||
ns := len(self.s)
|
|
||||||
if err := self.array(); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
if self.p = self.lspace(self.p); self.p >= ns {
|
|
||||||
return types.ERR_EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for empty array */
|
|
||||||
if self.s[self.p] == ']' {
|
|
||||||
self.p++
|
|
||||||
return _ERR_NOT_FOUND
|
|
||||||
}
|
|
||||||
|
|
||||||
var err types.ParsingError
|
|
||||||
/* allocate array space and parse every element */
|
|
||||||
for i := 0; i < idx; i++ {
|
|
||||||
|
|
||||||
/* decode the value */
|
|
||||||
if _, err = self.skip(); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for EOF */
|
|
||||||
self.p = self.lspace(self.p)
|
|
||||||
if self.p >= ns {
|
|
||||||
return types.ERR_EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for the next character */
|
|
||||||
switch self.s[self.p] {
|
|
||||||
case ',':
|
|
||||||
self.p++
|
|
||||||
case ']':
|
|
||||||
self.p++
|
|
||||||
return _ERR_NOT_FOUND
|
|
||||||
default:
|
|
||||||
return types.ERR_INVALID_CHAR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
206
ast/sort.go
Normal file
206
ast/sort.go
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 ByteDance Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ast
|
||||||
|
|
||||||
|
// Algorithm 3-way Radix Quicksort, d means the radix.
|
||||||
|
// Reference: https://algs4.cs.princeton.edu/51radix/Quick3string.java.html
|
||||||
|
func radixQsort(kvs PairSlice, d, maxDepth int) {
|
||||||
|
for len(kvs) > 11 {
|
||||||
|
// To avoid the worst case of quickSort (time: O(n^2)), use introsort here.
|
||||||
|
// Reference: https://en.wikipedia.org/wiki/Introsort and
|
||||||
|
// https://github.com/golang/go/issues/467
|
||||||
|
if maxDepth == 0 {
|
||||||
|
heapSort(kvs, 0, len(kvs))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
maxDepth--
|
||||||
|
|
||||||
|
p := pivot(kvs, d)
|
||||||
|
lt, i, gt := 0, 0, len(kvs)
|
||||||
|
for i < gt {
|
||||||
|
c := byteAt(kvs[i].Key, d)
|
||||||
|
if c < p {
|
||||||
|
swap(kvs, lt, i)
|
||||||
|
i++
|
||||||
|
lt++
|
||||||
|
} else if c > p {
|
||||||
|
gt--
|
||||||
|
swap(kvs, i, gt)
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// kvs[0:lt] < v = kvs[lt:gt] < kvs[gt:len(kvs)]
|
||||||
|
// Native implemention:
|
||||||
|
// radixQsort(kvs[:lt], d, maxDepth)
|
||||||
|
// if p > -1 {
|
||||||
|
// radixQsort(kvs[lt:gt], d+1, maxDepth)
|
||||||
|
// }
|
||||||
|
// radixQsort(kvs[gt:], d, maxDepth)
|
||||||
|
// Optimize as follows: make recursive calls only for the smaller parts.
|
||||||
|
// Reference: https://www.geeksforgeeks.org/quicksort-tail-call-optimization-reducing-worst-case-space-log-n/
|
||||||
|
if p == -1 {
|
||||||
|
if lt > len(kvs) - gt {
|
||||||
|
radixQsort(kvs[gt:], d, maxDepth)
|
||||||
|
kvs = kvs[:lt]
|
||||||
|
} else {
|
||||||
|
radixQsort(kvs[:lt], d, maxDepth)
|
||||||
|
kvs = kvs[gt:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ml := maxThree(lt, gt-lt, len(kvs)-gt)
|
||||||
|
if ml == lt {
|
||||||
|
radixQsort(kvs[lt:gt], d+1, maxDepth)
|
||||||
|
radixQsort(kvs[gt:], d, maxDepth)
|
||||||
|
kvs = kvs[:lt]
|
||||||
|
} else if ml == gt-lt {
|
||||||
|
radixQsort(kvs[:lt], d, maxDepth)
|
||||||
|
radixQsort(kvs[gt:], d, maxDepth)
|
||||||
|
kvs = kvs[lt:gt]
|
||||||
|
d += 1
|
||||||
|
} else {
|
||||||
|
radixQsort(kvs[:lt], d, maxDepth)
|
||||||
|
radixQsort(kvs[lt:gt], d+1, maxDepth)
|
||||||
|
kvs = kvs[gt:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insertRadixSort(kvs, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertRadixSort(kvs PairSlice, d int) {
|
||||||
|
for i := 1; i < len(kvs); i++ {
|
||||||
|
for j := i; j > 0 && lessFrom(kvs[j].Key, kvs[j-1].Key, d); j-- {
|
||||||
|
swap(kvs, j, j-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pivot(kvs PairSlice, d int) int {
|
||||||
|
m := len(kvs) >> 1
|
||||||
|
if len(kvs) > 40 {
|
||||||
|
// Tukey's ``Ninther,'' median of three mediankvs of three.
|
||||||
|
t := len(kvs) / 8
|
||||||
|
return medianThree(
|
||||||
|
medianThree(byteAt(kvs[0].Key, d), byteAt(kvs[t].Key, d), byteAt(kvs[2*t].Key, d)),
|
||||||
|
medianThree(byteAt(kvs[m].Key, d), byteAt(kvs[m-t].Key, d), byteAt(kvs[m+t].Key, d)),
|
||||||
|
medianThree(byteAt(kvs[len(kvs)-1].Key, d),
|
||||||
|
byteAt(kvs[len(kvs)-1-t].Key, d),
|
||||||
|
byteAt(kvs[len(kvs)-1-2*t].Key, d)))
|
||||||
|
}
|
||||||
|
return medianThree(byteAt(kvs[0].Key, d), byteAt(kvs[m].Key, d), byteAt(kvs[len(kvs)-1].Key, d))
|
||||||
|
}
|
||||||
|
|
||||||
|
func medianThree(i, j, k int) int {
|
||||||
|
if i > j {
|
||||||
|
i, j = j, i
|
||||||
|
} // i < j
|
||||||
|
if k < i {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if k > j {
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxThree(i, j, k int) int {
|
||||||
|
max := i
|
||||||
|
if max < j {
|
||||||
|
max = j
|
||||||
|
}
|
||||||
|
if max < k {
|
||||||
|
max = k
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxDepth returns a threshold at which quicksort should switch
|
||||||
|
// to heapsort. It returnkvs 2*ceil(lg(n+1)).
|
||||||
|
func maxDepth(n int) int {
|
||||||
|
var depth int
|
||||||
|
for i := n; i > 0; i >>= 1 {
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
return depth * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// siftDown implements the heap property on kvs[lo:hi].
|
||||||
|
// first is an offset into the array where the root of the heap lies.
|
||||||
|
func siftDown(kvs PairSlice, lo, hi, first int) {
|
||||||
|
root := lo
|
||||||
|
for {
|
||||||
|
child := 2*root + 1
|
||||||
|
if child >= hi {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if child+1 < hi && kvs[first+child].Key < kvs[first+child+1].Key {
|
||||||
|
child++
|
||||||
|
}
|
||||||
|
if kvs[first+root].Key >= kvs[first+child].Key {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
swap(kvs, first+root, first+child)
|
||||||
|
root = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func heapSort(kvs PairSlice, a, b int) {
|
||||||
|
first := a
|
||||||
|
lo := 0
|
||||||
|
hi := b - a
|
||||||
|
|
||||||
|
// Build heap with the greatest element at top.
|
||||||
|
for i := (hi - 1) / 2; i >= 0; i-- {
|
||||||
|
siftDown(kvs, i, hi, first)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop elements, the largest first, into end of kvs.
|
||||||
|
for i := hi - 1; i >= 0; i-- {
|
||||||
|
swap(kvs, first, first+i)
|
||||||
|
siftDown(kvs, lo, i, first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that Pair.Key is NOT pointed to Pair.m when map key is integer after swap
|
||||||
|
func swap(kvs PairSlice, a, b int) {
|
||||||
|
kvs[a].Key, kvs[b].Key = kvs[b].Key, kvs[a].Key
|
||||||
|
kvs[a].Value, kvs[b].Value = kvs[b].Value, kvs[a].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two strings from the pos d.
|
||||||
|
func lessFrom(a, b string, d int) bool {
|
||||||
|
l := len(a)
|
||||||
|
if l > len(b) {
|
||||||
|
l = len(b)
|
||||||
|
}
|
||||||
|
for i := d; i < l; i++ {
|
||||||
|
if a[i] == b[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return a[i] < b[i]
|
||||||
|
}
|
||||||
|
return len(a) < len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteAt(b string, p int) int {
|
||||||
|
if p < len(b) {
|
||||||
|
return int(b[p])
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue