-1

我正在从 GAE 数据存储中读取行,我想按字母数字对它们进行排序。

假设我有这样的事情:

key      name      description    sequence  
===========================================  
ASD..    maths1    it is maths    chap21.1  
ASD..    maths2    it is maths    chap21.10  
ASD..    maths3    it is maths    chap21.2  

我希望结果在序列字段上按字母数字排序,如下所示:

key      name      description    sequence  
===========================================  
ASD..    maths1    it is maths    chap21.1  
ASD..    maths3    it is maths    chap21.2 
ASD..    maths2    it is maths    chap21.10   
4

4 回答 4

6

使用ISO/IEC 14651:2011构造序列排序键。例如,

package main

import (
    "fmt"
    "sort"
)

const maxByte = 1<<8 - 1

func isDigit(d byte) bool {
    return '0' <= d && d <= '9'
}

func SequenceKey(key string) string {
    sKey := make([]byte, 0, len(key)+8)
    j := -1
    for i := 0; i < len(key); i++ {
        b := key[i]
        if !isDigit(b) {
            sKey = append(sKey, b)
            j = -1
            continue
        }
        if j == -1 {
            sKey = append(sKey, 0x00)
            j = len(sKey) - 1
        }
        if sKey[j] == 1 && sKey[j+1] == '0' {
            sKey[j+1] = b
            continue
        }
        if sKey[j]+1 > maxByte {
            panic("SequenceKey: invalid key")
        }
        sKey = append(sKey, b)
        sKey[j]++
    }
    return string(sKey)
}

type Chapter struct {
    Key         string
    Name        string
    Description string
    Sequence    string
    SequenceKey string `datastore:"-"`
}

type Chapters []*Chapter

var chapters = Chapters{
    {Key: "ASD..", Name: "maths1", Description: "it is maths", Sequence: "chap21.1"},
    {Key: "ASD..", Name: "maths2", Description: "it is maths", Sequence: "chap21.10"},
    {Key: "ASD..", Name: "maths3", Description: "it is maths", Sequence: "chap21.2"},
}

func (s Chapters) Len() int {
    return len(s)
}

func (s Chapters) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

type BySequenceKey struct{ Chapters }

func (s BySequenceKey) Less(i, j int) bool {
    return s.Chapters[i].SequenceKey < s.Chapters[j].SequenceKey
}

func main() {
    for _, chapter := range chapters {
        chapter.SequenceKey = SequenceKey(chapter.Sequence)
    }
    fmt.Println("Unsorted:")
    for _, chapter := range chapters {
        fmt.Printf("  sequence: %#v\n", chapter.Sequence)
        fmt.Printf("    sort key: %#v\n", chapter.SequenceKey)
        fmt.Printf("      name: %#v\n", chapter.Name)
    }
    fmt.Println("Sorted:")
    sort.Sort(BySequenceKey{chapters})
    for _, chapter := range chapters {
        fmt.Printf("  sequence: %#v\n", chapter.Sequence)
        fmt.Printf("    sort key: %#v\n", chapter.SequenceKey)
        fmt.Printf("      name: %#v\n", chapter.Name)
    }
}

输出:

Unsorted:
  sequence: "chap21.1"
    sort key: "chap\x0221.\x011"
      name: "maths1"
  sequence: "chap21.10"
    sort key: "chap\x0221.\x0210"
      name: "maths2"
  sequence: "chap21.2"
    sort key: "chap\x0221.\x012"
      name: "maths3"
Sorted:
  sequence: "chap21.1"
    sort key: "chap\x0221.\x011"
      name: "maths1"
  sequence: "chap21.2"
    sort key: "chap\x0221.\x012"
      name: "maths3"
  sequence: "chap21.10"
    sort key: "chap\x0221.\x0210"
      name: "maths2"
于 2013-08-21T21:06:34.010 回答
2

Peter 的回答让我想起了go.text存储库的collat ​​e 包,它是 Go 官方存储库的子存储库,其中包含一些目前正在开发的包。这个包提供了你需要的一切,并且完全支持语言环境和 unicode。

您可以使用CompareString方法对内存中的行切片进行排序,但更好的方法是将排序键(可以照常进行比较的字节序列)存储为附加列,并让 GAE 完成其余的你。

package main

import (
    "code.google.com/p/go.text/collate"
    "code.google.com/p/go.text/locale"
    "fmt"
)

func main() {
    locId := locale.Make("en-US")
    col := collate.New(locId)
    col.SetOptions(collate.Numeric | collate.IgnoreCase)

    keys := []string{"chap21.1", "chap21.10", "chap21.2", "chap21.03.3",
        "chap21.3.03", "chap21.03.03"}

    buf := new(collate.Buffer)
    for i := 0; i < len(keys); i++ {
        fmt.Println(keys[i], col.KeyFromString(buf, keys[i]))
    }
}

编辑:我刚刚仔细研究了实现,并且大多数方法(包括SetOptions数字排序的处理)尚未实现。所以这个答案可能有点太早了,但至少你知道你将来如何对你的行进行排序;)

于 2013-08-21T22:48:04.177 回答
-1

根据参考资料,您可以简单地对您需要的属性进行排序:

从文档:

// Order alphabetically by last name:
q := datastore.NewQuery("Person").Order("LastName")

所以在你的例子中,你可以有一些类似的东西:

func queryAll(r *http.Request) ([]string, error) {
    c := appengine.NewContext(r)
    res := make([]string, 0, 0)
    t := datastore.NewQuery("YourStructure").Order("Sequence").Run(c)
    for {
        var s YourStructure
        if _, err := t.Next(&s); err == datastore.Done {
            // Done iterating
            return res, nil
        } else if err != nil {
            // An error happened
            return nil, err
        }
        res = append(res, s.Name) 
    }
    panic("unreachable")
}
于 2013-08-21T17:53:33.107 回答
-1

如果您没有太多行数,您可能可以检索所有行并将它们存储在一个切片中。然后,您可以通过实现sort.Interface并调用sort.Sort函数对 RAM 中的这些条目进行排序。如果您需要一个示例,请查看sort.IntSlice的来源。

棘手的部分可能是定义字母数字排序顺序。我不知道它的确切定义(而且我无法在这么短的时间内查找它),但无论如何我都尝试过实现它。以下是您可能用于 less 方法的代码:

package main

import "log"

func less(a, b string) bool {
    i, j := 0, 0
    for i < len(a) && j < len(b) {
        numeric, numA, numB := false, 0, 0
        for i < len(a) && a[i] >= '0' && a[i] <= '9' {
            numA = numA*10 + int(a[i]) - '0'
            numeric = true
            i++
        }
        for j < len(b) && b[j] >= '0' && b[j] <= '9' {
            numB = numB*10 + int(b[j]) - '0'
            numeric = true
            j++
        }
        if numeric {
            if numA != numB {
                return numA < numB
            }
            continue
        }
        if a[i] != b[j] {
            return a[i] < b[j]
        }
        i++
        j++
    }
    return i == len(a) && j != len(b)
}

var tests = []struct {
    a, b string
    r1, r2 bool
}{
    {"bar", "foo", true, false},
    {"foo100", "foo10", false, true},
    {"foo100a", "foo100b", true, false},
    {"foo", "foo", false, false},
    {"100", "100", false, false},
    {"foo5", "foo12", true, false},
    {"foo5", "fo3", true, false},
    {"foo", "foo8", true, false},
}

func main() {
    for i := range tests {
        if less(tests[i].a, tests[i].b) != tests[i].r1 {
            log.Fatalf("test %d failed", i)
        }
        if less(tests[i].b, tests[i].a) != tests[i].r2 {
            log.Fatalf("reverse test %d failed", i)
        }

    }
}

我不确定代码是否足以满足您的需求,或者您是否需要处理更复杂的情况,但它至少可以为您自己的修改提供一个良好的起点。

于 2013-08-21T18:33:38.020 回答