【问题标题】:alphanumeric sorting in GoGo中的字母数字排序
【发布时间】:2013-08-21 10:46:00
【问题描述】:

我正在从 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   

【问题讨论】:

    标签: google-app-engine go


    【解决方案1】:

    使用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"
    

    【讨论】:

    • chap 21.1 chap 21.2 chap 21.10 这应该是结果
    • @AshvinSingh:是的,您已经告诉我们了,这就是 ISO/IEC 14651 算法的工作原理。如果按序列字段对行进行排序,您将得到 chap21.1、chap21.10、chap21.2,这不是您想要的。但是,如果您通过使用 SequenceKey 函数从序列字段构造的序列排序键对行进行排序,您将得到 chap21.1、chap21.2、chap21.10,这正是您想要的。如果您仍然不明白这一点,请更新您的问题,通过示例详细说明您认为 ISO/IEC 14651 算法不起作用的原因。
    • @peterSO 很好的答案!这是datastore:"-" GAE 特定的吗?我不明白它的用途。你介意详细说明一下吗?
    • @topskip 这是一个标签,在此描述:golang.org/ref/spec#Struct_types。它们是元数据,在 Go 中没有任何意义,但可以通过反射检索。 GAE 将这些用作properties,以便在 GAE 中存储的数据与您的程序中使用的数据有不同的表示,如下所述:developers.google.com/appengine/docs/go/datastore/…
    【解决方案2】:

    Peter 的回答让我想起了 go.text 存储库的 collate 包,这是 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 和数字排序的处理)还没有实现。所以这个答案可能有点太早了,但至少你知道你将来如何对你的行进行排序;)

    【讨论】:

      【解决方案3】:

      根据the reference,您可以简单地对您需要的属性进行排序:

      来自文档:

      // 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")
      }
      

      【讨论】:

        【解决方案4】:

        如果您没有太多的行数,您可能可以检索所有行并将它们存储在一个切片中。然后,您可以通过实现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)
                }
        
            }
        }
        

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

        【讨论】:

          猜你喜欢
          • 2017-06-05
          • 2015-06-22
          • 2012-10-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-05
          相关资源
          最近更新 更多