⚖️

stralnumcmp.vim

2024/01/22に公開

10km9km、あるいはpart10.mp4part9.mp4など、数字を含んだ文字列をいい感じに比較します。

function s:stralnumcmp(a, b) abort
  let [a, b] = [a:a, a:b]
  while strcharlen(a .. b) > 0
    let an = a->matchstr('^\d\+')
    let bn = b->matchstr('^\d\+')
    let as = an ?? a->matchstr('^\D\+')
    let bs = bn ?? b->matchstr('^\D\+')
    let c = (!!an && !!bn) ? str2nr(an) - str2nr(bn)
          \ : as < bs ? -1
          \ : as > bs ? 1
          \ : 0
    if c != 0
      return c
    endif
    let a = slice(a, strcharlen(an ?? as))
    let b = slice(b, strcharlen(bn ?? bs))
  endwhile
  return strcharlen(a:a) - strcharlen(a:b)
endfunction

2つの文字列を引数とし、それらが等しいなら0、前者が後になる場合は正の値、前者が先になる場合は負の値を返します。
例えば、10km9kmであれば、10kmのほうが後になるので、s:stralnumcmp('10km', '9km')は正の値を返します。
文字列同士の順序を比較する場所(主にsort()の第二引数)に使えます。

test
call assert_true(s:stralnumcmp('10km', '9km') > 0)
call assert_true(s:stralnumcmp('part10.mp4', 'part9.mp4') > 0)
call assert_true(s:stralnumcmp('9km', '10km') < 0)
call assert_true(s:stralnumcmp('part9.mp4', 'part10.mp4') < 0)

" just a ascii sort
call assert_true(s:stralnumcmp("JPN-Z", "JPN-A") > 0)
" just a ascii sort
call assert_true(s:stralnumcmp("2", "1") > 0)
call assert_true(s:stralnumcmp("vic1", "vic0") > 0)
call assert_true(s:stralnumcmp("vic10", "vic9") > 0)
call assert_true(s:stralnumcmp("10", "9") > 0)
call assert_true(s:stralnumcmp("E10", "E9") > 0)
call assert_true(s:stralnumcmp("10E", "9E") > 0)
call assert_true(s:stralnumcmp("1.10", "1.9") > 0)
call assert_true(s:stralnumcmp("aa11", "11bba") > 0)
call assert_true(s:stralnumcmp("aaaa", "aaaa") == 0)
" not a octal
call assert_true(s:stralnumcmp("010", "8") > 0)
" longer is bigger
call assert_true(s:stralnumcmp("10000", "010000") < 0)
" longer is bigger
call assert_true(s:stralnumcmp("010000", "10000") > 0)
" comparing chars < '0'
call assert_true(s:stralnumcmp("!11", "aa") < 0)
call assert_true(s:stralnumcmp("!11", "11") < 0)

参考

こちらのrepoのlua版の実装(およびテスト)を参考にVim script版を作成しました。

https://github.com/yasuoka/stralnumcmp

Discussion