webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

Rubyでどう書く?配列からターゲットの周囲5件を抜き出す

こんにちは、エンジニアのさもです。

仕事で次のような要件のメソッドを実装しました。

  • 配列[1,2,3,4,5,6,7,8,9]がある。
  • この配列から、指定した数字(実際は検索結果の配列に対して、一つのオブジェクトを指定)を含む周囲5件を抜き出した配列を作る

(具体例)

  • 1を指定した場合は、[1,2,3,4,5]
  • 2を指定した場合は、[1,2,3,4,5]
  • 5を指定した場合は、[3,4,5,6,7]
  • 8を指定した場合は、[5,6,7,8,9]
  • 9を指定した場合は、[5,6,7,8,9]

今回は、実装したメソッドの紹介と、Minitestでのテストコードを紹介します。

テスト駆動ではなかったので、後で完成したテストコードを載せています。

スポンサーリンク

目次

実装例

コード

以下が完成したコードです。

def arround_values(array, target, length)
  return [] if array.size == 0
  center_index = array.find_index(target)
  margin_size = length / 2

  # 前方から優先的に一致させる
  ahead_start_index = [0, center_index - margin_size].max
  ahead_end_index = ahead_start_index + (length - 1)
  ahead_match = array[ahead_start_index..ahead_end_index]

  # 後方から一致させる
  back_end_index = [(array.size-1), center_index + margin_size].min
  back_start_index = back_end_index - (length - 1)
  back_match = array[back_start_index..back_end_index]

  # 長い配列が取れた方を返す
  ahead_match.size > back_match.size ? ahead_match : back_match
end

解説

引数は、対象とする配列、ターゲット、返す配列の(最大の)長さとしています。

以下、lengthは5と仮定します。

7~9行目は、ターゲットが前の方にある場合の処理です。真ん中ぐらいにターゲットがあった場合もここで正しく取れます。

  • 7行目では、配列の先頭から抜き出すか、ターゲットの2つ手前から抜き出すかを選択しています。
  • 8行目ではスタートから5つ後ろのインデックスを決定しています。
  • 9行目で、配列から目的の配列を抜き出しています。

12~14行目はターゲットが後ろの方にある場合の対応です。

17行目で、2つの抜き出した配列のうち、長い方(上手く取れている方)を返しています。

テストを書く

せっかくですから、テストを書いておきましょう。

Rubyではテストを書くためのクラスが標準で付いてきます。私が使用したバージョンは2.2.3だったのでMinitest::Testクラスを継承したテストクラスを作ります。

require 'minitest/autorun'
class ArroundValuesTest < Minitest::Test
  def setup
    @array = ['apple', 'banana', 'car', 'handle', 'user', 'cat', 'dog', 'bird']
    @length = 5
  end

  def test_target_first
    target = 'apple'
    result = arround_values(@array, target, @length)
    assert_equal result, ['apple', 'banana', 'car', 'handle', 'user']
  end

  def test_target_second
    target = 'banana'
    result = arround_values(@array, target, @length)
    assert_equal result, ['apple', 'banana', 'car', 'handle', 'user']
  end

  def test_target_middle
    target = 'handle'
    result = arround_values(@array, target, @length)
    assert_equal result, ['banana', 'car', 'handle', 'user', 'cat']
  end

  def test_target_last
    target = 'bird'
    result = arround_values(@array, target, @length)
    assert_equal result, ['handle', 'user', 'cat', 'dog', 'bird']
  end

  def test_target_before_last
    target = 'dog'
    result = arround_values(@array, target, @length)
    assert_equal result, ['handle', 'user', 'cat', 'dog', 'bird']
  end

  def test_short_array
    target = 'banana'
    result = arround_values(['apple', 'banana', 'car'], target, @length)
    assert_equal result, ['apple', 'banana', 'car']
  end

  def test_blank_array
    target = 'banana'
    result = arround_values([], target, @length)
    assert_equal result, []
  end

end

テストを各場所は、メソッド定義のすぐ後で問題ないです。

実行は ruby 保存したファイル名で実行できます。

では、ざっくりとテストコードの説明をします。

  • setupメソッドは、各テスト実行前に実行してくれます。
  • テストはtest_から始まるメソッドを実装することで書くことが出来ます。
  • assert_equalは引数同士が等しい事をテストするメソッドです。次のようにtrue, falseをテストするassertメソッドを使って書いてもいいですが、assert_equalの方がテストの内容が分かりやすくなります。
assert result == ['apple', 'banana', 'car']

最後に

仕事でRailsを触っていると、基本的にはDBから取ってきて表示or登録・更新だけで済んでしまいますが、たまに頭を使う実装があると楽しくなりますね。

以上、ざっくりと私が書いたコードを紹介しました。

読者登録をしていただけると、ブログを続ける励みになりますので、よろしくお願いします。