Imagine an alphabet of words.
Example:
a ==> 1
b ==> 2
c ==> 3
z ==> 26
ab ==> 27
ac ==> 28
az ==> 51
bc ==> 52
For letters of this imaginary alphabet that are more than one character long, we may use the recursion:
XnXn-1..X1 =
max(n-1)
+ (max(n-1) - last (n-1)-character letter before
the first (n-1)-character letter after a)
... + (max(n-1) - last (n-1)-character letter before the
first (n-1)-character letter after the-letter-before-Xn)
+ 1 + ((Xn-1..X1) - first (n-1)-character letter after Xn)
where max(1) = z, max(2) = yz...
Haskell code:
import Data.List (sort)
import qualified Data.MemoCombinators as M
firstAfter letter numChars = take numChars $ tail [letter..]
lastBefore letter numChars = [toEnum (fromEnum letter - 1) :: Char]
++ reverse (take (numChars - 1) ['z','y'..])
max' numChars = reverse (take numChars ['z','y'..])
loop letter numChars =
foldr (\a b -> b
+ index (max' numChars)
- index (lastBefore (head $ firstAfter a numChars) numChars)
) 0 ['a'..letter]
index = M.list M.char index' where
index' letter
| null (drop 1 letter) = fromEnum (head letter) - 96
| letter /= sort letter = 0
| otherwise = index (max' (len - 1))
+ loop (head $ lastBefore xn 1) (len - 1)
+ 1
+ index (tail letter) - index (firstAfter xn (len - 1))
where len = length letter
xn = head letter
Output:
*Main> index "abcde"
17902
*Main> index "abcdefghijklmnopqrstuvwxyz"
67108863
(0.39 secs, 77666880 bytes)