2019/4/17 追記
改良版書きました!!
前回、前々回 の続きですが、マルチバイトの数字には対応できていなかったので対応させました。こちら を参考にしつつ、Windows エクスプローラー準拠の動作を実現させました。
前回のコードのままだと strconv.Atoi()
でマルチバイト数字はパースエラーとなってしまうので、その前段に golang.org/x/text/width
パッケージを挟むことで、全角数字を半角数字にしてから数字に変換しています。golang にはこういうパッケージが標準/準標準でたくさんあるのでありがたいですね。
package main import ( "fmt" "sort" "strconv" "unicode" "golang.org/x/text/width" ) func getRune(ru []rune, i int) (rune, int) { for x := i; x < len(ru); x++ { if unicode.IsSpace(ru[x]) { continue } return ru[x], x } return rune(0), len(ru) } func natualSort0(a, b []rune) int { var ca, cb rune var ai, bi int for { if len(a) <= ai && len(b) <= bi { return 0 } if len(a) <= ai { return -1 } if len(b) <= bi { return 1 } ca, ai = getRune(a, ai) cb, bi = getRune(b, bi) if unicode.IsNumber(ca) && unicode.IsNumber(cb) { aj := ai + 1 for len(a) > aj { if !unicode.IsNumber(rune(a[aj])) { aj-- break } aj++ } bj := bi + 1 for len(b) > bj { if !unicode.IsNumber(rune(b[bj])) { bj-- break } bj++ } sa := a[ai:aj] if len(sa) == 0 { sa = []rune{a[ai]} } ia, err := strconv.Atoi(width.Narrow.String(string(sa))) if err != nil { panic(err) } sb := b[bi:bj] if len(sb) == 0 { sb = []rune{b[bi]} } ib, err := strconv.Atoi(width.Narrow.String(string(sb))) if err != nil { panic(err) } if ia < ib { return -1 } if ia > ib { return 1 } // Windows エクスプローラーの仕様と合わせるために // 今回は削除 // if len(a[ai:aj]) < len(b[bi:bj]) { // return -1 // } // if len(a[ai:aj]) > len(b[bi:bj]) { // return -1 // } // ai = aj // bi = bj } if ca < cb { return -1 } if ca > cb { return 1 } ai++ bi++ } } func naturalSort(strings []string) func(i, j int) bool { return func(i, j int) bool { return natualSort0([]rune(strings[i]), []rune(strings[j])) <= 0 } } func dumpByNaturalSort(strings []string) { sort.Slice(strings, naturalSort(strings)) for i, s := range strings { if i != 0 { fmt.Print(" < ") } fmt.Printf("%+v", s) } fmt.Println() } func main() { dumpByNaturalSort([]string{"a20", "a10", "a2", "a1b", "a1a", "a1", "a0", "a"}) // a < a0 < a1 < a1a < a1b < a2 < a10 < a20 dumpByNaturalSort([]string{"x8-y8", "x2-y08", "x2-y7", "x2-g8"}) // x2-g8 < x2-y7 < x2-y08 < x8-y8 dumpByNaturalSort([]string{"1.3", "1.1", "1.02", "1.010", "1.002", "1.001"}) // 1.001 < 1.1 < 1.002 < 1.02 < 1.3 < 1.010 dumpByNaturalSort([]string{"001", "01", "1", "10", "010", "2", "002"}) // 001 < 01 < 1 < 002 < 2 < 010 < 10 dumpByNaturalSort([]string{"a001", "a01", "a1", "a10", "a010", "a2", "a002"}) // a001 < a01 < a1 < a002 < a2 < a010 < a10 dumpByNaturalSort([]string{"3", "1", "10", "2", "20"}) // 1 < 2 < 3 < 10 < 20 }