⚖️
stralnumcmp.vim
10km
と9km
、あるいはpart10.mp4
とpart9.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、前者が後になる場合は正の値、前者が先になる場合は負の値を返します。
例えば、10km
と9km
であれば、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版を作成しました。
Discussion