Edited at

Excel列名変換問題をRubyで解いてみた

More than 1 year has passed since last update.


Excel列名変換問題とは

Excelって行名は番号なのに列名がAlphabetじゃないですか.

なので,1列目は'A',2列目は'B',みたいな変換がしたくなるわけです.(axlsx使っててなりました)

ぐぐったところどうやらこれをExcel列名変換問題というらしく,いろんな言語で実装してる人がいるみたいです.

おもしろそうなのでRubyで実装してみました.


なにはともあれTest Code

関数のIN/OUTがはっきりしていて内部状態を持たない純粋関数みたいなものなので,まずまっさきにTest Codeから整備しておくべきである,というわけで ↓ のようなTest Codeを作りました.(Input/Outputの組み合わせはぐぐりました)


Integer->String

def test_convert_col_num_to_alphabet

assert_raises { convert_col_num_to_alphabet(0) }
assert_equal('A', convert_col_num_to_alphabet(1))
assert_equal('Z', convert_col_num_to_alphabet(26))
assert_equal('AA', convert_col_num_to_alphabet(27))
assert_equal('AZ', convert_col_num_to_alphabet(52))
assert_equal('BA', convert_col_num_to_alphabet(53))
assert_equal('ZZ', convert_col_num_to_alphabet(702))
assert_equal('AAA', convert_col_num_to_alphabet(703))
end


String->Integer

def test_convert_alphabet_to_col_num

assert_raises { convert_alphabet_to_col_num('') }
assert_raises { convert_alphabet_to_col_num(nil) }
assert_equal(1, convert_alphabet_to_col_num('A'))
assert_equal(26, convert_alphabet_to_col_num('Z'))
assert_equal(27, convert_alphabet_to_col_num('AA'))
assert_equal(52, convert_alphabet_to_col_num('AZ'))
assert_equal(53, convert_alphabet_to_col_num('BA'))
assert_equal(702, convert_alphabet_to_col_num('ZZ'))
assert_equal(703, convert_alphabet_to_col_num('AAA'))
end


できあがった実装

できるかぎりシンプルにしたかったのですが ↓ のくらいのところでいったん満足しました.


Integer->String

ALPHABET_COUNT = 26

ALPHABET_CODE_OFFSET = 'A'.ord

def convert_col_num_to_alphabet(count)
raiase "count is 0 or minus" if count <= 0

ret = ''

loop {
count = count - 1

remainder = count % ALPHABET_COUNT
char = (ALPHABET_CODE_OFFSET + remainder).chr
ret = char << ret

count = count / ALPHABET_COUNT
break if count == 0
}

return ret
end



String->Integer

def convert_alphabet_to_col_num(alphabet)

raise "alphabet is nil" if alphabet.nil?
raise "alphabet is empty" if alphabet.empty?

ret = 0
size = alphabet.size

alphabet.each_char { |char|
ret += (char.ord - ALPHABET_CODE_OFFSET + 1) * ALPHABET_COUNT ** (size - 1)
size -= 1
}

return ret
end


とりあえずこれで上のTest CodeもPassしました.

文字を数字として扱うのに.ordと.chrというAPIを使っていますが,これを見つけられなかったらもっと長くなってたかも.

['', 'A', 'B', 'C', ... 'Z']

っていう配列作ってIndexでアクセスしたり,

' ABC...Z'

っていう文字列作ってIndexでアクセスしたりしてたかも.

(こっちのほうが直感的でわかりやすい,って話もあるか)

count = count - 1

の処理が直感的にわかりにくいのが悩みどころ.


やってみた感想

Algorithm考えるのは楽しいです.

昔XML Parserを自力実装したことがありましたがそれと同じ雰囲気.(車輪の再発明感も)

しかしこんなLogic,超汎用なんだからどっかのFrameworkにAPI実装されててもおかしくないですよね. ← たぶんある

---///