Integer to Alpha Representation

I think you can use IntegerDigits directly if you add an offset, and then use the 3rd argument of IntegerDigits to only return the needed digits. For instance, consider base 3. If we use an offset (in trinary notation) of:

$$11 \text{$\cdots $1112}$$

Then the trinary notation for the offset integers $1, 2, \text{$\dots $, 13}$ is:

$$1120, 1121, 1122, 1200, 1201, 1202, 1210, 1211, 1212, 1220, 1221, 1222, \ 2000$$

Notice that if we take the last digit of the first 3, the last two digits of the next 9, and then the last 3 digits of the last number, we get:

$$0, 1, 2, 00, 01, 02, 10, 11, 12, 20, 21, 22, 000$$

With the replacements {0->"a", 1->"b", 2->"c"} we get the desired alphabet representation. The following code uses this idea:

intToAlphabet[i_, alphabet:{__String}|Automatic:Automatic]:=Module[{a = Replace[alphabet,Automatic:>ToUpperCase@Alphabet[]],base,maxDigits,offset,indices},
    base=Length[a];
    maxDigits = Floor[Log[base,(base-1)Max[i]+1.1]];
    offset=(1+(base-2)(base^maxDigits-1)/(base-1)) ;
    indices=1+IntegerDigits[i+offset, base, Floor[Log[base,(base-1)i+1.1]]];
    If[ListQ@i,
        StringJoin[a[[#]]]&/@indices,
        StringJoin[a[[i]]]
    ]
]

One comment on the code. Computing Floor[{Log[3,5], Log[3,15], ..}] is slow while the almost equivalent Floor[{Log[3, 5`], Log[3, 15`], ..}] will be very fast. The only issue with using real numbers instead of integers is to make sure something like Floor[Log[3, 9`]] evaluates to 2 and not 1 due to precision issues. I take care of this by adding a bit of slop.

At any rate, some examples:

intToAlphabet[Range[20], {"a","b"}]

{"a", "b", "aa", "ab", "ba", "bb", "aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb", "aaaa", "aaab", "aaba", "aabb", "abaa", "abab"}

r = intToAlphabet[Range[10^5]]; //AbsoluteTiming
r[[{285, 448, 211}]]

{0.259129, Null}

in agreement with a few examples in your question.

{"JY", "QF", "HC"}


A memoized recursive approach seems faster:

dictionary = AssociationThread[Range[26] -> CharacterRange["A", "Z"]]

Clear[replace]
replace[n_Integer /; n <= 26] := replace[n] = dictionary[n]
replace[n_Integer] := replace[n] = Module[{quot, rem},
   {quot, rem} = QuotientRemainder[n, 26];
   If[rem == 0, quot = quot - 1; rem = 26];
   If[quot > 0, replace[quot] <> replace[rem], replace[rem]]
 ]

Comparative timing tests:

nums = RandomInteger[{10000000, 20000000}, 100000];

RepeatedTiming[replace /@ nums;]
(* Out: {0.0944, Null} *)

RepeatedTiming[intToAlpha@nums;]
(* Out: {2.34, Null} *)

Mod and Quotient support offsets, allowing:

core = Quotient[Sow @ Mod[#, 26, 1]; #, 26, 1] &;

fn[n_Integer?Positive] := (
  NestWhile[core, n, Positive]
    // Reap
    // Extract[{2, 1}]
    // FromCharacterCode[# + 64] &
    // StringReverse
 )

Test:

Accumulate[26^Range[4]]

fn /@ %

fn /@ (%% + 1)
{26, 702, 18278, 475254}

{"Z", "ZZ", "ZZZ", "ZZZZ"}

{"AA", "AAA", "AAAA", "AAAAA"}

I don't expect this to be competitively fast as written but the algorithm should be compilable.