5
$\begingroup$

I would like to create a list of the first $n$ letters of the english alphabet. Importantly, I would like that in case $n$ is bigger than the size of the alphabet, the letters will start repeating in the following fashion

$\{...x,y,z,aa,ab,ac,ad,ae...\}$

And in case of a very large $n$, they will start repeating like

$\{...zx,zy,zz,aaa,aab,aac,aad,aae...\}$

and so on.

Is there an easy way to do so?

$\endgroup$
8
  • $\begingroup$ Possible duplicate, generate-a-combination-of-letters-by-a-number $\endgroup$
    – chyanog
    Commented Mar 15, 2022 at 8:38
  • 1
    $\begingroup$ Can't you just use BaseForm[n,26]? Then you need to remove the superscript, turn into a string and replace $1,2,...,a,...p$ with $a,b,...,z$. Looks easier than the other answers? Unless I'm missing something $\endgroup$
    – Jojo
    Commented Mar 15, 2022 at 9:43
  • $\begingroup$ @Joe BaseForm[10, 26] displays as "a" (with a subscript). Furthermore, BaseForm is a special wrapping form, not a string. But even if it were easy to turn it into a string, that still doesn't address the problem that only 25 "digits" out of the 26 will ever appear in the first/leftmost position. $\endgroup$
    – lericr
    Commented Mar 15, 2022 at 13:22
  • $\begingroup$ @chyanog It does indeed look like a duplicate. It evan discusses the same topics in the comment threads. $\endgroup$
    – lericr
    Commented Mar 15, 2022 at 13:24
  • $\begingroup$ @lericr yes that's why I said 'remove the subscript, turn into a string and replace 1,2,...,a,...p with a,b,...,z'. But yeah I agree that it doesn't work to display everything you want in the left column $\endgroup$
    – Jojo
    Commented Mar 15, 2022 at 13:46

5 Answers 5

3
$\begingroup$

You may use QuotientRemainder with FromLetterNumber and a bit of recursion.

ClearAll[baseAlphabetForm]
SetAttributes[baseAlphabetForm, Listable];
baseAlphabetForm[expr_Integer?NonNegative] :=
  If[
  expr == 0
  , ""
  , Module[
   {q, r}
   , {q, r} = QuotientRemainder[expr, 26]
   ; If[r == 0, (q -= 1; r = 26;)]
   ; StringJoin[{baseAlphabetForm[q], ToUpperCase@FromLetterNumber[r]}]
     ]
   ]

Then

baseAlphabetForm[10]
"J"

and

baseAlphabetForm[{7288884863, 1844844242797219}]
{"WOLFRAM", "MATHEMATICA"}
baseAlphabetForm[{6500564,3472301,129693,17311,562,6500564,3472301,8262,17311, 81056}]

and

baseAlphabetForm[Range[100]] //
 Multicolumn[#, Frame -> All, Appearance -> "Horizontal"] &

Mathematica graphics

Hope this helps.

$\endgroup$
3
$\begingroup$

Updated answer

The problem I was struggling with when trying to use IntegerDigits was that the leading digit for any number in "normal form" can never be zero. The fancy Mod stuff couldn't correct for that, because no matter the modulus, there are only 25 options for the leading digit. There is a padding option for IntegerDigits, but ultimately just "manually" correcting the problem (see ModStep) was simpler. I've been staring at letter sequences long enough now that my eyes may have glossed over, so there may still be an error, but I don't see it if it's there.

IntToLetterSequence[n_Integer?Positive] := 
  StringJoin@Part[Alphabet[], Rest@FixedPoint[ModStep, {n}]];
ModStep[{d_, 0, ds___}] := {d - 1, 26, ds};
ModStep[ds : {0, __}] := ds;
ModStep[ds : {d_, ___}] := 
  ReplacePart[ds, 1 -> Splice[QuotientRemainder[d, 26]]]

Original answer didn't rollover correctly

I'd write a function like this:

ModulusLetter[n_Integer?(GreaterThan[26])] := 
  StringJoin[Part[Alphabet[], 1 + MapAt[# - 1 &, IntegerDigits[n - 1, 26], 1]]];
ModulusLetter[n_Integer?Positive] := Alphabet[][[n]]

It's a bit messy, because the "modulus" of the first character is different than the subsequent characters. Might be a more elegant way with Mod or QuotientRemainder or something.

$\endgroup$
9
  • $\begingroup$ Thank you! This is almost perfect. There is only a small issue. This ModulusLetter[n] you defined skips the "za, zb, zc, ..." etc part. With this I meant to say that ModulusLetter[676]=yz, and ModulusLetter[677]=aaa, while it should be ModulusLetter[677]=za. A similar problem arises when you pass from three letters to four. It will miss all the "zaa, zab, ..." etc sector. Maybe there is a minor typo somewhere. I'm still trying to understand your code, sorry. $\endgroup$ Commented Mar 14, 2022 at 19:09
  • $\begingroup$ Sorry, didn't test enough. I think the best alternative is to insert explicit logic like @Edmund's answer, but I have an inkling that you don't need recursion. I'll look again later if nothing better is posted in the meantime. $\endgroup$
    – lericr
    Commented Mar 14, 2022 at 20:22
  • $\begingroup$ @FedericoCarta If you're still looking for an answer, check the update. $\endgroup$
    – lericr
    Commented Mar 14, 2022 at 23:14
  • $\begingroup$ @Edmund In the end my logic converged to something similar to yours, but the recursion is handled by FixedPoint. $\endgroup$
    – lericr
    Commented Mar 14, 2022 at 23:19
  • $\begingroup$ @FedericoCarta Another point...I assumed that you wanted a function that produced the nth string, but in re-reading your question, you only mentioned wanting a list. There are probably faster/cleaner ways to create a list of size k rather than map my function over Range[k]. $\endgroup$
    – lericr
    Commented Mar 14, 2022 at 23:34
3
$\begingroup$

Another way:

Block[{nn = 1000, alphabet = CharacterRange["a", "z"]},
 Flatten@Table[
   StringJoin@ alphabet[[1 + IntegerDigits[n - 1, 26, k]]],
   {k, 1 + Log[26, nn]},
   {n, Min[26^k, nn - (26^k - 26)/25]}]
 ]
(*
{"a", "b", "c", ..., "x", "y", "z", "aa", "ab", "ac", ..., 
 "zx", "zy", "zz", "aaa", "aab", "aac", ..., "alj", "alk", "all"}
*)
$\endgroup$
2
$\begingroup$

Definitely not the best answer as it creates more information than needed and then scraps it, but possibly at least worth putting out there. The name is inspired by a certain spreadsheet program's way of labeling the columns when there are more than 26, as it is the same as here.

excelSequence[n_?Positive] := Flatten[
  NestList[
    Outer[StringJoin, Alphabet[], #] & (*Take the previous string and append each letter of the alphabet*),
    Alphabet[],
    Ceiling@Log[26, 25 n/26 + 1] - 1 (*smallest number of nestings needed*)
  ]
][[;; n]]

Minimal nestings

Since the Outer gives us lists of length $26^k$ (after being flattened), and the NestList tacks on the previous lists, the total length of the final list follows the geometric series

$$26^1 + 26^2 + 26^3 + \ldots + 26^{n_0} = \frac{26}{25} \left(26^{n_0}-1\right)$$

. Then, since NestList needs to undergo an integer number of nestings $n_0 - 1$ (e.g. 0 nestings gives the alphabet) and we need at least the input number $n$ of elements, our goal is to find $n_0$ such that $n \leq \frac{26}{25} \left(26^{n_0}-1\right) \implies \log _{26}\left(\frac{25 n}{26}+1\right) \leq n_0$. Now we don't want any more information than we have to have with this method, so we choose the smallest integer $n_0$ such that $\log _{26}\left(\frac{25 n}{26}+1\right) \leq n_0$, which is the ceiling of left side of the inequality. So the smallest amount of nestings needed is $n_0 = \left\lceil \log _{26}\left(\frac{25 n}{26}+1\right)\right\rceil$.

Efficiency

Due to the nesting nature, a lot of extra information can first be produced and then deleted, making this method possibly very inefficient depending on the input. That being said, it reaches peak efficiency (because no information is deleted) when inputting terms in the geometric series: $\{26,702,18278,475254, ..., \frac{26}{25} \left(26^n-1\right)\}$, where it reaches minimal efficiency when using inputs just above these terms because the algorithm needs to generate the next power of 26 elements and then delete all but one.

Examples

excelSequence[10]

{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}

excelSequence[100]

{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj", "ak",
"al", "am", "an", "ao", "ap", "aq", "ar", "as", "at", "au", "av",
"aw", "ax", "ay", "az", "ba", "bb", "bc", "bd", "be", "bf", "bg",
"bh", "bi", "bj", "bk", "bl", "bm", "bn", "bo", "bp", "bq", "br",
"bs", "bt", "bu", "bv", "bw", "bx", "by", "bz", "ca", "cb", "cc",
"cd", "ce", "cf", "cg", "ch", "ci", "cj", "ck", "cl", "cm", "cn",
"co", "cp", "cq", "cr", "cs", "ct", "cu", "cv"}

excelSequence[1000] // Short

{a,b,c,d,e,f,g,h,i,j,<<981>>,ald,ale,alf,alg,alh,ali,alj,alk,all}

And because I know you're curious

RepeatedTiming@excelSequence[10^5] // Short

{0.187,{a,b,c,d,<<99992>>,eqxa,eqxb,eqxc,eqxd}}

$\endgroup$
1
$\begingroup$

Concise version:

Table[Reverse@Mod[NestWhileList[Floor[(# - 1)/26.] &, n, # > 26 &], 26, 1] + 96,
  {n, 10^3}] // FromCharacterCode

{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,<<970>>,akx,aky,akz,ala,alb,alc,ald,ale,alf,alg,alh,ali,alj,alk,all}

Faster version:

cf = Compile[{{n, _Integer}},
   Module[{i = n, bag = Internal`Bag[Most@{0}]},
    While[i > 0,
     Internal`StuffBag[bag, Mod[i, 26, 1]];
     i = Floor[(i - 1)/26];
     ];
    Reverse@Internal`BagPart[bag, All] + 96
    ], RuntimeAttributes -> {Listable}
   ];

ans = FromCharacterCode@cf[Range[10^6]]; // AbsoluteTiming

{0.459288, Null}

{a,b,c,d,e,f,g,h,i,j,k,l,m,<<999975>>,bdwgc,bdwgd,bdwge,bdwgf,bdwgg,bdwgh,bdwgi,bdwgj,bdwgk,bdwgl,bdwgm,bdwgn}

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.