Excel列名変換問題

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修正