id:JunichiIto:20111102:1320253815 の「Excel列名変換問題で第2回社内プログラミングコンテストを開催してみた(前編)」を見て、やってみた。
Rubyで書くことにしたが、Ruby固有の機能は使わずCでも簡単に書き直せる程度で。
Excelの列名は、A,B,…,Z,AA,AB,…AZ,BA,… のようになっているが、これと番号 1,2,…,26,27,28,…,52,53,… との相互変換を行う。
まず、文字から番号は簡単。
ARGV.each do |arg| case arg when /^[A-Z]+$/ val = 0 arg.each_char do |c| val = val*26+c.ord-"@".ord end puts val end end
逆の変換は26で割っていくのだが、割る前に1始まりなので1を引く必要があるな。後で1足せば良いかということで、"@"の代わりに"A"を使う。
ARGV.each do |arg| case arg when /^[1-9][0-9]*$/ val = arg.to_i-1 ans = "" while val>0 ans = (val%26+"A".ord).chr+ans val /= 26 end puts ans end end
しかし、1 が Aでなく空になって、2〜26 は B〜Zになるものの、27 は AA じゃなくて BA になってしまう。全然だめ。
しばし考えて修正*1。
ARGV.each do |arg| case arg when /^[1-9][0-9]*$/ val = arg.to_i ans = "" while val>0 val -= 1 ans = (val%26+"A".ord).chr+ans #Ruby1.9.3でString#prependが出来たのでそれで書き直せる val /= 26 end puts ans end end
これが落とし穴か。
まとめると、元の問題とは引数の意味が少し違うがこんな所。
ARGV.each do |arg| case arg when /^[A-Z]+$/ val = 0 arg.each_char do |c| val = val*26+c.ord-"@".ord end puts val when /^[1-9][0-9]*$/ val = arg.to_i ans = "" while val>0 val -= 1 ans = (val%26+"A".ord).chr+ans val /= 26 end puts ans end end
2011-11-19 追記
何となくループで書いたけど、この手の物は再帰で書くべきだったか。関数型言語風。
def etoi(x) if x.size == 1 x.ord-"@".ord else etoi(x[0..-2])*26 + etoi(x[-1]) end end def itoe(x) if x<=26 (x+"@".ord).chr else itoe((x-1)/26) + itoe((x-1)%26+1) end end
*1:2011-11-17修正