文字列ソートで数字が含まれている箇所は数値で比較する方法

公開:2021/02/18
更新:2021/02/21
1 min読了の目安(約1300字TECH技術記事

ソフトウェアのバージョン番号(e.g. 1.9.5, 1.10.2)やファイル名(e.g. Mobile Mission Part1.m4a, Mobile Mission Part2.m4a)など文字列の中に数字が含まれていることは少なくないです。
しかしながら、文字列としてソート(辞書順ソート)してしまうと少し不便な並び順になってしまいます。
例えば、通し番号のついたファイルの場合は通常以下のように並ぶと思います。

  1. File_1
  2. File_10
  3. File_2
  4. File_3
  5. File_4
  6. File_5
  7. File_6
  8. File_7
  9. File_8
  10. File_9

直感的には、次のような順序の方がわかりやすいでしょう。

  1. File_1
  2. File_2
  3. File_3
  4. File_4
  5. File_5
  6. File_6
  7. File_7
  8. File_8
  9. File_9
  10. File_10

さらにFile_2_2のように枝番があったり、FileB (2-2 ver)のように書式の異なるファイル群があったりするなど、ファイル名のどこに数字が現れるか明確でないと処理するのが厄介に思えます。

しかし、とにかく「文字列のどこかに現れる数字は数値としてソートする」というルールであれば下記の方法でソートできます。

sort.ts
const compare = ( a: string, b: string ): number => {

  if ( a === b ) return 0

  const [ $a, $b ] = [ a, b ].map(
    text => text.match( /(\d+|\D+)/g )!.map(
      str => /^\d+$/.test( str ) ? parseInt( str, 10 ) : str
    )
  )

  const length = Math.min( $a.length, $b.length )

  for ( let i = 0; i < length; ++ i ) {

    const [ x, y ] = [ $a[ i ], $b[ i ] ]
    if ( x !== y ) { return x < y ? -1 : 1 }

  }

  return $a.length < $b.length ? -1 : 1

}

const list = Array( 10 ).fill( "File_" ).map( ( prefix, index ) => `${ prefix }${ index + 1 }` )

//望まないソート
console.log( list.sort() )

//望みのソート
console.log( list.sort( compare ) )

以上!
ぜひお試しください。