ようへいの日々精進XP

よかろうもん

2018 年 04 月 19 日(木)

ジョギング

  • 香椎浜 x 2 周
  • 最近は 29 分くらい (6.5 キロ位) なので, 朝からだとまあまあ早いペースかも

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

夕飯

  • 奥さんが体調イマイチだというので, うどんを作る

今日のるびぃ ~ exercism.io で世界を相手に Ruby のプログラム問題を解いていく (2) ~

exercism.io というプログラム問題ライブラリで提供されている Ruby の問題を解いていきたいと思います.

exercism.io

今日の問題 ~ Calculate the Hamming difference between two DNA strands. ~

Calculate the Hamming difference between two DNA strands.

2 つの塩基配列の Hamming differnce を計算しろってことらしいんだが, そもそも Hamming differnce って何?ってところからのスタートなのでかなり戸惑った...

It is found by comparing two DNA strands and counting how many of the
nucleotides are different from their equivalent in the other string.

    GAGCCTACTAACGGGAT
    CATCGTAATGACGGCCT
    ^ ^ ^  ^ ^    ^^

おそらく, 2 つの塩基配列を比較して, 各ヌクレオチドの違いの数を計算しろってことなんだろう...ということで, 問題を解いていく.

テスト

以下のようなテストが用意されている.

require 'minitest/autorun'
require_relative 'hamming'

# Common test data version: 2.0.1 f79dfd7
class HammingTest < Minitest::Test
  def test_empty_strands
    # skip
    assert_equal 0, Hamming.compute('', '')
  end

...

  def test_large_distance_in_off_by_one_strand
    # skip
    assert_equal 9, Hamming.compute('GGACGGATTCTG', 'AGGACGGATTCT')
  end

  def test_disallow_first_strand_longer
    # skip
    assert_raises(ArgumentError) { Hamming.compute('AATG', 'AAA') }
  end

実装

現在の自分の Ruby 力だと, 以下が精一杯.

class Hamming
  def self.compute(a1, a2)
    raise ArgumentError if a1.size != a2.size

    array1 = a1.split('')
    array2 = a2.split('')
    diff_flag = []
    array1.each_with_index do |v, i|
      if v == array2[i.to_i]
        diff_flag << 0
      else
        diff_flag << 1
      end
    end
    return diff_flag.count(1)
  end
end

module BookKeeping
  VERSION = 3
end

以下のような動きになる.

  1. Hamming.compute クラスメソッドの引数は同じ文字列数が必須 (同じで無い場合, AregumentError で例外)
  2. 引数を配列化する
  3. array1 の each_with_index で回して, インデックス番号を利用して array2 の要素を取り出していく
  4. 3 で取り出した要素を array1 の要素と比較して, diff_flag 配列に 0 (一致していた場合), 1 (不一致だった場合) を append していく
  5. 最後に diff_flag で不一致となった場合の要素 1 の数をカウントする

テストを走らせてみる.

$ ruby hamming_test.rb 
Run options: --seed 50416

# Running:

................

Finished in 0.001120s, 14281.6851 runs/s, 14281.6851 assertions/s.

16 runs, 16 assertions, 0 failures, 0 errors, 0 skips

LGTM.

publish

publish すると, 自動的に以下のようなコメントが付いた.

Whenever you are looping through a collection and find yourself writing a conditional (if or unless) nested inside of the loop, take a moment to look through the available enumerable methods.

There are some very handy ones that might let you simplify.

どうやら, もっと良いソリューションがあるようだ.

写経

他の挑戦者のコードをざーっと見ていて, 以下のような書き方が一番美しく見えたので写経.

exercism.io

class Hamming
  def self.compute(a1, a2)
    raise ArgumentError if a1.size != a2.size
    a1.chars.zip(a2.chars).count { |x, y| x != y }
  end
end

module BookKeeping
  VERSION = 3
end
  • String#chars で文字列を文字で分割して配列で返す
  • 配列を Array#zip でマージした上で, ブロック内で配列の要素を比較し, そのブロック内の結果を count メソッドでカウントしている

一度, 配列にした上で要素を比較していくというアプローチは自分のコードと同じであるが, こちらの方が超シンプルで美しい.

exercism.io って

世界中の人の書き方が学べてイイ感じ.