LoginSignup
4
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-07

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実装されててもおかしくないですよね. ← たぶんある

---///

4
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0