Excelの列番号形式の文字列を作るときに、参考にApache POIのCellReference#formatAsString(から呼ばれているconvertNumToColString)の実装を読んでみたら面白かったのでメモ。
Excelの列番号はAから始まり、A, B, C, ..., X, Y, Z, AA, AB, AC, ...のように増加していく。
この記号列に対して、配列の添え字っぽく0, 1, 2, ...と0から順に整数値を対応させていくと、
- A = 0
- B = 1
- ...
- Y = 24
- Z = 25
- AA = 26 = 26 * 1 + 0
- ...
- AZ = 51 = 26 * 1 + 25
- BA = 52 = 26 * 2 + 0
- BB = 53 = 26 * 2 + 1
- ...
となり、A〜Zで各桁を表現する26進数だと思うと、最下位の桁ではA, B, ...を0, 1, ...として扱い、それ以外の桁ではA, B, ...を1, 2, ...と扱うことになり統一感がない。
Apache POIのCellReference#formatAsString
はこのようにはせず、対応する数値を1から始めることにして、
- A = 1
- B = 2
- ...
- Y = 25
- Z = 26
- AA = 27 = 26 * 1 + 1
- ...
- AZ = 52 = 26 * 1 + 26
- BA = 53 = 26 * 2 + 1
- BB = 54 = 26 * 2 + 2
- ...
として、各桁で文字の意味が同じになるようにしている。
あとは、0を表す文字がなく、ふつうの26進数とは桁上がりのタイミングがずれる(1026がZになったり、2026がAZになったりする)ことに注意すると、変換は以下のようになる(代入があると読みにくいのでSchemeで再帰で書き直した)。
(define alphabets "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
(define (integer->excel-column n)
(define (f i)
(string-ref alphabets (- i 1)))
(let loop ((n n)
(rs '()))
(cond
((zero? n)
(error "cannot represent as excel-column" n))
((<= 1 n 26)
(list->string (cons (f n) rs)))
(else
(let-values (((q r) (quotient&remainder n 26)))
(if (zero? r)
(loop (- q 1) (cons (f 26) rs))
(loop q (cons (f r) rs))))))))
逆変換は何も考えずに基数変換っぽく変換する。
(define (excel-column->integer col)
(string-fold (lambda (c knil)
(+ (string-index alphabets c)
1
(* knil 26)))
0
col))
0始まりの添字に変換したい場合は適当に変換前に+1したり変換後に-1したりすればよい。