アルファベットを1〜26の数字に変換して名前リストの総和を求めるメソッドをテスト駆動開発する(Project Euler 問22)

スポンサーリンク

今回の問題

Names scores

Using names.txt (right click and ‘Save Link/Target As…’), a 46K text file containing over five-thousand first names, begin by sorting it into alphabetical order. Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list to obtain a name score.

For example, when the list is sorted into alphabetical order, COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would obtain a score of 938 × 53 = 49714.

What is the total of all the name scores in the file?

5000個以上の名前が書かれている46Kのテキストファイル filenames.txt を用いる. まずアルファベット順にソートせよ.

のち, 各名前についてアルファベットに値を割り振り, リスト中の出現順の数と掛け合わせることで, 名前のスコアを計算する.

たとえば, リストがアルファベット順にソートされているとすると, COLINはリストの938番目にある. またCOLINは 3 + 15 + 12 + 9 + 14 = 53 という値を持つ. よってCOLINは 938 × 53 = 49714 というスコアを持つ.

ファイル中の全名前のスコアの合計を求めよ.

書いた内容

テストの作成と名前昇順並び替えメソッド

名前のスコリングをする前にリストを昇順にソートする必要があります。
Minitest を用いて、まずそのテストを仮で記載しました。

namesメソッドの引数に昇順済になったリストが返ることを想定し、

  • はじめの名前:AARONであること
  • 最後の名前:ZULMAであること
  • 名前の数:5163であること

を期待します。

require 'minitest/autorun'
require './problem'

require 'minitest/reporters'
Minitest::Reporters.use!

class ProblemTest < Minitest::Test
  def test_sorted_names_array
    assert_equal 'AARON', names.first
    assert_equal 'ZULMA', names.last
    assert_equal 5163, names.size
  end
end

名前のリストをKernel#openメソッドとIO#readメソッドで取り出します。
ダブルクォーテーションやカンマが混じっているので
String#scanメソッドの引数に半角英数字を指定して名前の配列を作成します。

最後に、Array#sortメソッドでで昇順に変更しています。

これでテストは通ります。

def names
  File.open('names.txt').read.scan(/\w+/).sort
end

問題文のCOLINを例にスコアの計算ロジックをテスト

問題文の COLIN を例にスコアのテストを記載しました。

Array#indexメソッドで COLIN が配列の何番目に含まれるかを処理させると
indexは 0 から始まるので 937 が返るはずです。

また、score_of_nameメソッドに、名前・インデックスを指定することで
題意のスコアが返ることを期待しました。

require 'minitest/autorun'
require './problem'

require 'minitest/reporters'
Minitest::Reporters.use!

class ProblemTest < Minitest::Test
  def test_sorted_names_array
    assert_equal 'AARON', names.first
    assert_equal 'ZULMA', names.last
    assert_equal 5163, names.size
  end

  def test_score_of_colin
    assert_equal 937, names.index('COLIN')
    assert_equal 49_714, score_of_name('COLIN', 938)
  end
end

score_of_nameメソッドの中身は、
引数 name の文字列をString#charsメソッドで一文字ずつ分解して
それぞれをArray#map、Enumerable#injectで畳み込み演算しています。

mapの中身ですが、String#ordメソッドを用いています。
返り値はコードポイントという整数値で、大文字アルファベットは65から始まります。
参考:アルファベットのコードポイント、コード表

これらを組み合わせることでテストを通します。

def names
  File.open('names.txt').read.scan(/\w+/).sort
end

def score_of_name(name, index)
  worth = name.chars.map { |alphabet| alphabet.ord - 64 }.inject(:+)
  worth * index
end

最終解

Enumerator#with_indexメソッドでインデックスを1からとしてeachを回すことで

def names
  File.open('names.txt').read.scan(/\w+/).sort
end

def score_of_name(name, index)
  worth = name.chars.map { |alphabet| alphabet.ord - 64 }.inject(:+)
  worth * index
end

answer = 0
names.each.with_index(1) do |name, index|
  answer += score_of_name(name, index)
end
puts answer
#=> 871198282

最終的な答えは 871198282 と出すことが出来ました。

※問22ブランチのプルリクエスト

          
    

スポンサーリンク

  
Scroll Up