自然順ソート(マルチバイト対応改良版 & Windows エクスプローラー準拠)

何度も同じようなブログを連投してしまいますが、先のブログには大きな数字を使うとちゃんと動かないという欠点がありました。今回はそれを改良したバージョンになります。

先のバージョンでは、strconv.Atoi を使用している関係で どうしても数値が int の範囲を超えることができませんでした。

今回はstrconv.Atoi を使用せずに、文字列比較のみで数字を順番に並べます。

Windows エクスプローラーを色々触って確かめていると、どうもゼロサプレスした値を右寄せ比較しているだけなのではないか?ということに気付きました。

なので、数字比較の処理の中で頭の 0 は読み飛ばした後、右寄せ比較を行うようにしました。

そうすると、WIndows エクスプローラーのソートに近づけることができました。

package main

import (
    "fmt"
    "sort"
    "unicode"
)

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 compareNumber(a, b []rune, ai, bi int) int {
    aj := ai
    for len(a) > aj {
        if a[aj] != '0' {
            break
        }
        aj++
    }

    bj := bi
    for len(b) > bj {
        if b[bj] != '0' {
            break
        }
        bj++
    }

    bias := 0

    for {
        if (len(a) <= aj || !unicode.IsNumber(a[aj])) && (len(b) <= bj || !unicode.IsNumber(b[bj])) {
            return bias
        }

        if len(a) <= aj || !unicode.IsNumber(a[aj]) {
            return -1
        }

        if len(b) <= bj || !unicode.IsNumber(b[bj]) {
            return 1
        }

        ca := a[aj]
        cb := b[bj]

        if ca < cb {
            if bias == 0 {
                bias = -1
            }
        }

        if ca > cb {
            if bias == 0 {
                bias = 1
            }
        }

        aj++
        bj++
    }
}

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) {
            if ret := compareNumber(a, b, ai, bi); ret != 0 {
                return ret
            }
        }

        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", "x01-y09"})
    // x01-y09 < 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_a", "001", "01", "1", "10", "010", "2", "002"})
    // 001 < 001_a < 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

    dumpByNaturalSort([]string{"4294967294", "1", "4294967296", "4294967299", "11111111111111111222222222222222"})
    // 1 < 4294967294 < 4294967296 < 4294967299 < 11111111111111111222222222222222
}