◆ A,B,C,AA,...,CC,AAA,... みたいなラベルを作る

エクセルの列名みたいなアルファベットを使ったラベルを作ろうと思います
しかし あの名付け順は以外と複雑です

複雑

Z だと長いので C までで考えます

0 ✖
1 A
2 B
3 C
4 AA
5 AB
6 AC
7 BA
8 BB
9 BC

という割り当てになります

4 進数のようでちょっと違います
4 進数だと 10 のように桁が上がったときの 0 がありますが 今回の場合はありません
入れるとしたらこういうことになってしまいます

0 0  ""
1 1 A
2 2 B
3 3 C
4 10 A""
5 11 AA
6 12 AB
7 13 AC
8 20 B""
9 21 BA

"" は空白文字です

0 に A を割り当てて 3 進数にしてもこうなります

0 0   A  A
1 1 B B
2 2 C C
3 10 AA BA
4 11 AB BB
5 12 AC BC
6 20 BA CA
7 21 BB CB
8 22 BC CC
9 100 CA BAA

C の次を AA にすると 00 になってしまいます
かと言って 1 = B なので C の次を BA にするのもおかしいです

そういうわけで単純に N 進数で置き換えられません

規則性

3 進数でみたときに 10 が BA になりますがこれを AA にずらせばうまくいってるように見えます
例えば 8 のときは CC ですが 2 桁目を一つずらせば BC です
9 のときは BAA ですがこれも 2 桁目を一つずらせば CA です

3 桁目付近では

10 101 CB  BAB
11 102 CC BAC
12 110 AAA BBA
13 111 AAB BBB
14 112 AAC BBC
15 120 ABA BCA

11 のときは BAC ですがこれの 2 桁目をずらせば CC です
12 のときは BBA ですがこれの 2 桁目をずらせば BAA です
3 桁目でもまたずれが起きました

桁が上がるたびにそれぞれ 1 つずらせば良さそうです

完成品

これで作ったものがこちら

function label(n) {
const rec = (n, indexes) => {
indexes.unshift(n % 3)
const s = ~~(n / 3)
return s > 0 ? rec(s - 1, indexes) : indexes
}
return rec(n, []).map(e => "ABC"[e]).join("")
}

label(0)
// A

label(1)
// B

label(2)
// C

label(11)
// CC

label(12)
// AAA

label(38)
// CCC

label(39)
// AAAA

label(100)
// CACB

チェック

なんとなく 本当に桁数が上がってもちゃんとあってるのかな と心配になったので別の方法と比較しみてました
1 ずつ数を増やして D になると 桁を上げる作りです

function* seqLabel() {
let a = [0]
while (true) {
yield a.map(e => "ABC"[e]).reverse().join("")

let flag = true
a = a.map(e => {
if (!flag) return e
if (e === 2) {
flag = true
return 0
} else {
flag = false
return e + 1
}
})
if (flag) a.push(0)
}
}

function* take(it, n) {
let c = 0
for (const value of it) {
if (c++ >= n) return
yield value
}
}

const assert = (expect_true, msg) => {
if (!expect_true) throw new Error("assert failure: " + msg)
}

let c = 0
for (const x of take(seqLabel(), 1_000_000)) {
assert(label(c++) === x, `failed at ${x}`)
}
console.log("OK")
// OK

100 万まで問題なかったのできっと大丈夫なはず
今回は 3 で固定してますが ここを 26 などに置き換えるとアルファベットを何文字使うか変えれます