背景
プログラムで処理された結果をみている時に「1.txt」「11.txt」「2.txt」と並んでいると悲しい気分になりますよね。昨日ちょうど悲しくなったので、ファイル名を直感的に並べるロジックを考えてみました。
実行結果
実行結果を見てもらえば説明は不要かと思います。
[
"aaa.txt",
"aaa.xlsx",
"aaa(1).txt",
"aaa(1).xlsx",
"aaa(2).txt",
"aaa(2).xlsx",
"aaa(10).txt",
"aaa(10).xlsx",
"aaa1.txt",
"aaa1.xlsx",
"aaa2.txt",
"aaa2.xlsx",
"v1.0.0",
"v1.0.0-SNAPSHOT",
"v1.0.1",
"v1.0.10",
"v1.2.1",
"v1.10.1"
]
もっと良いコード
コメントで教えてもらいました。a.localeCompareのnumericオプションでいけちゃいます。
console.log(JSON.stringify([
"aaa(10).xlsx",
"aaa1.xlsx",
"aaa.xlsx",
"aaa(10).txt",
"v1.0.0-SNAPSHOT",
"v1.2.1", "aaa.txt",
"aaa1.txt",
"aaa(2).xlsx",
"aaa2.xlsx",
"aaa(1).xlsx",
"aaa(1).txt",
"v1.10.1",
"v1.0.10",
"aaa2.txt",
"aaa(2).txt",
"v1.0.1",
"v1.0.0"
].sort((a, b) => a.localeCompare(b, [], {numeric: true})), null, 2))
車輪の再開発なコード
とはいえ、他の言語で実装する人にはちょっと参考になる、、、かもしれない
import path from "path";
export function numberPadding(n: number | string, l: number) {
const nStr = `${n}`
// 想定している桁数を超えた場合にはpaddingせずにそのまま返す
return "0".repeat(Math.max(0, l - nStr.length)) + nStr
}
function naturalSortKey(str: string, paddingLength: number): string {
// 数字のソート順を直感的にするために、0パディングをかける
// A1.txt -> A0001.txt
// A2.txt -> A0002.txt
// A11.txt -> A0011.txt
return str.replace(/[0-9]+/g, (substring: string) => {
return numberPadding(substring, paddingLength)
})
}
export function byNaturalFilename(aStr: string, bStr: string): number {
/*
連番の場合、最初の一つには番号がついていないケースがある。
A.txt
A(1).txt
このような場合にも対処するため、拡張子以外が一致している場合には拡張子でソート、それ以外は拡張子抜きでソートする
*/
const aExt = path.extname(aStr)
const aBase = path.basename(aStr, aExt)
const bExt = path.extname(bStr)
const bBase = path.basename(bStr, bExt)
// 文字列の長さ以上にパディングが必要になることはない
const paddingLength = Math.max(aStr.length, bStr.length)
if (aBase === bBase) {
return naturalSortKey(aExt, paddingLength).localeCompare(naturalSortKey(bExt, paddingLength))
}
return naturalSortKey(aBase, paddingLength).localeCompare(naturalSortKey(bBase, paddingLength))
}
export function byNaturalFilenameReverse(aStr: string, bStr: string): number {
return byNaturalFilename(bStr, aStr)
}
console.log(JSON.stringify([
"aaa(10).xlsx",
"aaa1.xlsx",
"aaa.xlsx",
"aaa(10).txt",
"v1.0.0-SNAPSHOT",
"v1.2.1", "aaa.txt",
"aaa1.txt",
"aaa(2).xlsx",
"aaa2.xlsx",
"aaa(1).xlsx",
"aaa(1).txt",
"v1.10.1",
"v1.0.10",
"aaa2.txt",
"aaa(2).txt",
"v1.0.1",
"v1.0.0"
].sort(byNaturalFilename), null, 2))