Skip to content

Instantly share code, notes, and snippets.

@glts
Last active January 1, 2016 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glts/8085844 to your computer and use it in GitHub Desktop.
Save glts/8085844 to your computer and use it in GitHub Desktop.
Vim autoload library for base-k number systems without zero
" Tiny library for conversion of numbers of bijective base-k number systems
" (number systems without zero) to decimal integers and vice-versa. Examples:
"
" base 3: 1 2 3 11 12 13 21 22 23 31 32 33 111 112 113 ...
" base 5 (digits {a b c d e}): a b c d e aa ab ac ad ae ba ...
"
" The public interface is numerals#CreateConverter(digitstring) which creates
" a {converter} for the number system defined by the digitstring.
"
" {converter}.ValueOf(int) - converts an integer to a base-k number string
" {converter}.ToInteger(str) - converts a base-k number string to an integer
" {converter}.Inc(str) - increments a base-k number string by 1
function! s:ValidNumberRegexp(digitstring) abort
let l:digitre = join(map(split(a:digitstring, '\zs'), 'escape(v:val,"\\")'), '\|')
return '\V\^\%(' . l:digitre . '\)\*\$'
endfunction
function! s:ValidDigitString(digitstring) abort
" Make sure all digits are unique
let l:seen = {}
for i in range(len(a:digitstring))
if has_key(l:seen, a:digitstring[i])
return 0
endif
let l:seen[a:digitstring[i]] = 1
endfor
return 1
endfunction
function! s:ToInteger(str) dict abort
if a:str !~# self._validnrre
echohl ErrorMsg
echomsg "Invalid argument for this base"
echohl None
return -1
endif
let l:result = 0
let l:n = len(a:str)
for i in range(l:n)
let l:result += float2nr(pow(self._base, l:n-i-1)) * self._digitvals[a:str[i]]
endfor
return l:result
endfunction
function! s:ValueOf(int) dict abort
if a:int < 0
echohl ErrorMsg
echomsg "Invalid argument for this base"
echohl None
return
endif
let l:basef = str2float(self._base . '.0')
let l:result = []
let l:q = a:int
while l:q > 0
let l:qnext = float2nr(ceil(l:q/l:basef)) - 1
call insert(l:result, self._digits[l:q - l:qnext*self._base])
let l:q = l:qnext
endwhile
return join(l:result, '')
endfunction
function! s:Inc(str) dict abort
if a:str !~# self._validnrre
echohl ErrorMsg
echomsg "Invalid argument for this base"
echohl None
return -1
endif
let l:digits = split(a:str, '\zs')
let l:addone = 1
let l:len = len(l:digits)
let i = 0
while l:addone
let l:index = l:len - i - 1
if l:index < 0
" Add extra digit with value 1 in front
call insert(l:digits, self._digits[1])
break
endif
let l:val = self._digitvals[l:digits[l:index]]
let l:addone = l:val is# self._base
let l:digits[l:index] = self._digits[l:val%self._base + 1]
let i += 1
endwhile
return join(l:digits, '')
endfunction
" Takes a string with the digits of a bijective base-k number system, in order
" of their values 1 to k, and returns a new {converter} for that number
" system. The converter exposes the functions ValueOf() and ToInteger(), which
" convert from and to decimal, and the increment function Inc().
"
" :let g:base3 = numerals#CreateConverter('123')
" :echomsg g:base3.ValueOf(42)
" ~ '1113'
" :echomsg g:base3.Inc('1113')
" ~ '1121'
"
" The function {converter}.ValueOf(int) takes an integer and returns the
" corresponding base-k representation as a string.
" Reports an error for negative integers and returns false (0).
"
" :let g:base26 = numerals#CreateConverter('abcdefghijklmnopqrstuvwxyz')
" :echomsg g:base26.ValueOf(27)
" ~ 'aa'
"
" The function {converter}.ToInteger(str) takes a string representation of a
" base-k number and returns the corresponding positive integer.
" Reports an error when the string is an invalid number and returns -1.
"
" :echomsg g:base26.ToInteger('abacus')
" ~ 12815497
"
" The function {converter}.Inc(str) takes a string representation of a base-k
" number and returns a string with the number incremented by 1.
" Reports an error when the string is an invalid number and returns false (0).
"
" :echomsg g:base26.Inc('abz')
" ~ 'aca'
"
function! numerals#CreateConverter(digitstring) abort
if !s:ValidDigitString(a:digitstring)
echohl ErrorMsg
echomsg "Invalid digit string"
echohl None
return
endif
let l:base = len(a:digitstring)
let l:digits = range(l:base + 1)
let l:digitvals = {}
for i in range(l:base)
let l:digits[i+1] = a:digitstring[i]
let l:digitvals[a:digitstring[i]] = i + 1
endfor
let l:context = {
\ '_base': l:base,
\ '_digits': l:digits,
\ '_digitvals': l:digitvals,
\ '_validnrre': s:ValidNumberRegexp(a:digitstring),
\ 'ValueOf': function('s:ValueOf'),
\ 'ToInteger': function('s:ToInteger'),
\ 'Inc': function('s:Inc'),
\ }
return l:context
endfunction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment