【Ruby】1度だけ計算して2度目以降（ソートする時など）に1度目のデータを基に並び替えする方法

テラテイルで僕の苦戦した歴史が載っています。
https://teratail.com/questions/204044

備忘録として整理した内容で掲載します。同じく苦戦した方のお役に立てれば幸いです。

# タイトルの内容を分かりやすく
レースの走行時間の計算をします。次にその走行時間を元に早い順に並べ替えたいときの方法を今回は述べています。
ソートした時に2度目の計算が行われ、1度目の計算を基準にしたいのにできないというドツボにハマってしまいました。


# ゴールはタイム順に車体名が表示されること

下のスクショのような感じがゴールです。走行時間順に順位が表示されてますね。
ちなみにこの出力ができるまでに8時間くらい要したのですが端折って書きたいと思います。
同じ悩みを抱えてる方が15分くらいで実装できるように書くつもりですが、分からない場合はコメントください。
<img width="589" alt="スクリーンショット 2019-08-05 13.58.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256763/e021cc01-d6c6-6ad9-9804-99f0b2633b88.png">


# 最終ソースコード

`main.rb` が実行ファイルで、他のファイルを`require`しています。
`car.rb` の`def time` で距離の計算をしています。
車種ごとのファイル`ferrari.rb` `honda.rb` `nissan.rb` `toyota.rb` はスルーでも大丈夫です。
「ブレーキしたら速度が半分になる」という条件入れてるので少々式は複雑ですがコメントアウトでコードの意味書いてるので、分からない場合はご連絡ください。

```ruby:main.rb
# main.rb
require_relative 'car'
require_relative 'ferrari'
require_relative 'honda'
require_relative 'nissan'
require_relative 'toyota'

def main #(type, speed, price, capacity, height, passenger)
  h = Honda.new("Honda", 100, 100, 8, 100, 1)
  n = Nissan.new("Nissan", 120, 50, 5, 100, 1)
  f = Ferrari.new("Ferrari", 200, 2000, 2, 100, 1)
  t = Toyota.new("Toyota", 100, 300, 2, 100, 1)

  output_information(h,n,f,t) # ここで走行時間が計算されて保存される。car.rb の 「def time」 のelseで走行時間が計算される

  puts '-----順位-------'
  arr = [h, n, f, t]
  puts arr.sort_by{|x| x.time}.map(&:type) # 保存した走行時間をソートして表示。 上記の「output_information(h,n,f,t)」で走行時間が既に計算されてるからここではその計算結果が表示される
end

def output_information(h,n,f,t)
  puts "\t\t#{h.type}\t\t#{n.type}\t\t#{f.type}\t\t#{t.type}"
  puts "加速\t\t#{h.speed}\t\t#{n.speed}\t\t#{f.speed}\t\t#{t.speed}"
  puts "価格\t\t#{h.price}\t\t#{n.price}\t\t#{f.price}\t\t#{t.price}"
  puts "定員\t\t#{h.capacity}\t\t#{n.capacity}\t\t#{f.capacity}\t\t#{t.capacity}"
  puts "車高\t\t#{h.height}\t\t#{n.height}\t\t#{f.height}\t\t#{t.height}"
  puts "乗員\t\t#{h.passenger}\t\t#{n.passenger}\t\t#{f.passenger}\t\t#{t.passenger}"
  puts "走行時間\t\t#{h.time}\t\t#{n.time}\t\t#{f.time}\t\t#{t.time}"
end

if __FILE__ == $0
  main
end
```

```ruby:car.rb
# car.rb
# parent class
class Car
  attr_accessor :type, :speed, :price, :capacity, :height, :passenger

  def initialize(type, speed,  price, capacity, height, passenger)
    @type = type
    @speed = speed
    @price = price
    @capacity = capacity
    @height = height
    @passenger = passenger
  end

  def time #レースタイムを算出
    if @time # @time が存在するならその時間を表示（1回目は存在しない）
    @time
    
    else # @time が存在しないなら計算する → 1回目だけ計算して2回目以降は計算結果だけでソートできるようにしてる
    @distance = 10000 #走行距離
    @brake_speed = @speed/2 #減速時の速度
    random = Random.new
    @brake_time = random.rand(1..5) #今回はブレーキを踏む回数を1〜5回でランダム設定
    @time = @distance / (@brake_speed*@brake_time) + (@distance-@brake_speed*@brake_time) / @speed #タイム算出の計算式・・・ 減速時に進んだ時間 + 通常時に進んだ時間の和
    end
  end
end
```

```ruby:ferrari.rb
class Ferrari < Car
end
```

```ruby:honda.rb
class Honda < Car
end
```

```ruby:nissan.rb
class Nissan < Car
end
```
```ruby:toyota.rb
class Toyota < Car
end
```

# if を使わないと走行時間と順位表示が合わない

この場合、2位はNissanのはずなのにフェラーリが2位に表示されています。

<img width="742" alt="スクリーンショット 2019-08-02 18.43.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256763/402432be-2d96-69a9-e29e-6aad864bca61.png">

# 上手く行かない時のソースコードと原因

以下のコードだと`main.rb` の 
`output_information` で1度走行時間が計算されます。
`arr = [h, n, f, t]
  puts arr.sort_by{|x| x.time}.map(&:type)` で2度目の計算がされます。
出力画面の走行時間に表示されている数字が1回目、`arr` で順位表示させた時は2回目の計算がされているので順位が異なるのです。

```ruby:main.rb
require_relative 'car'
require_relative 'ferrari'
require_relative 'honda'
require_relative 'nissan'
require_relative 'toyota'

def main       #(type, speed, price, capacity, height, passenger)
  h = Honda.new("Honda", 100, 100, 8, 100, 1)
  n = Nissan.new("Nissan", 120, 50, 5, 100, 1)
  f = Ferrari.new("Ferrari", 200, 2000, 2, 100, 1)
  t = Toyota.new("Toyota", 100, 300, 2, 100, 1)

  output_information(h,n,f,t) #ここで1度目の計算がされる

  puts '-----順位-------'
  arr = [h, n, f, t]
  puts arr.sort_by{|x| x.time}.map(&:type) #ここで2度目の計算がされて、ここの数字が順位表示に使われるので走行時間と順位が合わなくなる
end

def output_information(h,n,f,t)
  puts "\t\t#{h.type}\t\t#{n.type}\t\t#{f.type}\t\t#{t.type}"
  puts "加速\t\t#{h.speed}\t\t#{n.speed}\t\t#{f.speed}\t\t#{t.speed}"
  puts "価格\t\t#{h.price}\t\t#{n.price}\t\t#{f.price}\t\t#{t.price}"
  puts "定員\t\t#{h.capacity}\t\t#{n.capacity}\t\t#{f.capacity}\t\t#{t.capacity}"
  puts "車高\t\t#{h.height}\t\t#{n.height}\t\t#{f.height}\t\t#{t.height}"
  puts "乗員\t\t#{h.passenger}\t\t#{n.passenger}\t\t#{f.passenger}\t\t#{t.passenger}"
  puts "走行時間\t\t#{h.time}\t\t#{n.time}\t\t#{f.time}\t\t#{t.time}"
end

if __FILE__ == $0
  main
end
```


```ruby:car.rb
# car.rb
# parent class
class Car
  attr_accessor :type, :speed, :price, :capacity, :height, :passenger, :distance, :brake_speed

  def initialize(type, speed,  price, capacity, height, passenger)
    @type = type
    @speed = speed
    @price = price
    @capacity = capacity
    @height = height
    @passenger = passenger
  end

  def time
  #レースタイムを算出。
  @distance = 10000 #走行距離
  @brake_speed = @speed/2 #減速時の速度
  random = Random.new
  @brake_time = random.rand(1..5) #今回はブレーキを踏む回数を1〜5回でランダム設定
  @distance / (@brake_speed*@brake_time) + (@distance-@brake_speed*@brake_time) / @speed #タイム算出の計算式・・・ 減速時に進んだ時間 + 通常時に進んだ時間の和
  end
end
```

# if を使って解決
`car.rb` の `def time` で「1度計算されたら2度目は計算されないようにする」というメソッドを作る。
具体的には「`if` 走行時間が計算されていないなら`else` 走行時間を計算する」という式を以下のソースコードのように作ります。（再掲になりますが）

```ruby:car.rb
# car.rb
# parent class
class Car
  attr_accessor :type, :speed, :price, :capacity, :height, :passenger

  def initialize(type, speed,  price, capacity, height, passenger)
    @type = type
    @speed = speed
    @price = price
    @capacity = capacity
    @height = height
    @passenger = passenger
  end

  def time #レースタイムを算出
    if @time # @time が存在するならその時間を表示（1回目は存在しない）
    @time
    
    else # @time が存在しないなら計算する → 1回目だけ計算して2回目以降は計算結果だけでソートできるようにしてる
    @distance = 10000 #走行距離
    @brake_speed = @speed/2 #減速時の速度
    random = Random.new
    @brake_time = random.rand(1..5) #今回はブレーキを踏む回数を1〜5回でランダム設定
    @time = @distance / (@brake_speed*@brake_time) + (@distance-@brake_speed*@brake_time) / @speed #タイム算出の計算式・・・ 減速時に進んだ時間 + 通常時に進んだ時間の和
    end
  end
end
```

# あとは array の使い方を工夫

これで解決です！

```ruby:main.rb
arr = [h, n, f, t]
puts arr.sort_by{|x| x.time}.map(&:type) 
#timeメソッドで走行時間を引っ張ってきて早い順にソートして車体名で表示
end
```