やりたいこと
06:23.19
みたいなものを383.19
秒のようにする。
2つの地点の通過タイムから区間タイムを計算する。
丸め誤差がでないように浮動小数点はできるだけ使わないようにする。
環境
macOS 10.15.7
Ruby 3.1.0
使用したメソッド
- String#partition
- 指定した文字を中心に、文字列を3分割して配列に収めるメソッド
- https://docs.ruby-lang.org/ja/latest/method/String/i/partition.html
- String#delete
- 文字列から指定した文字を削除するメソッド
- https://docs.ruby-lang.org/ja/latest/method/String/i/delete.html
- Numeric#modulo
- その他
- to_i
- to_f
- 有理数リテラルのr
[分] : [秒] . [コンマ秒]形式の文字列を秒に変換してみる
- "06:23.19"を秒に変換する
- 丸め誤差対策
- 文字列の段階で小数点を削除する
- ケタを戻すときは有理数を使う
code
str = "06:23.19"
strs = str.partition(":")
#=> ["06", ":", "23.19"]
#分のケタを秒にする
#整数にするため、100倍して2ケタ上げておく
min = strs[0].to_i * 60 * 100
#=> 36000
#秒の部分から小数点を削除して整数にする
sec = strs[2].delete('.').to_i
#=> 2319
sum_sec = min + sec
#=> 38319
#有理数を使って2ケタ戻す
puts "#{str}は#{(sum_sec * 0.01r).to_f}秒"
#=> 06:23.19は383.19秒
区間タイムを計算するメソッドをつくる
A地点の通過タイムが01:30.24。
B地点の通過タイムが03:12.14。
A-B間の区間タイムを計算する
メソッド
def to_interval(here, pre)
#分の単位を変換
min_here = here.partition(":")[0].to_i * 60 * 100
min_pre = pre.partition(":")[0].to_i * 60 * 100
#秒の単位を変換
sec_here = here.partition(":")[2].delete('.').to_i
sec_pre = pre.partition(":")[2].delete('.').to_i
#差を求める
interval_sec = (min_here + sec_here) - (min_pre + sec_pre)
#計算結果を[分]:[秒].[コンマ秒]形式に戻す
#秒を分に戻す
min = interval_sec / 6000
#秒の単位の桁を戻す
sec = interval_sec.modulo(6000) * 0.01r
interval_str = "#{sprintf("%02d", min)}:#{sprintf("%2.2f", sec)}"
return interval_str
end
つくったメソッドを実行してみる
run_to_interval.rb
here = "03:12.14"
pre = "01:30.24"
puts "A-B間のタイムは#{to_interval(here, pre)}です。"
ターミナル
$ ruby run_to_interval.rb
#=> A-B間のタイムは01:41.90です。
意図通りに動いた。
追記 [2022-03-10]
コメントで@scivolaさんからアドバイスをいただいたのでメソッドを書き換えました。
主な変更点
-
to_r
を使って文字列から有理数に変換 -
map(&:[メソッド])
を使って配列の要素を一気に変換 - 番号指定パラメータ
_1, _2
を使ってブロック内で要素を各々処理
[2022-03-11]
@scivolaさんからご指摘いただき、変更を更に修正しました。
詳しい内容はコメント欄参照。
修正点
-
/60
,modulo(60)
をdivmod(60)
へ -
sprintf
をString#%
へ
メソッド(改)と実行の結果
run_to_interval.rb
def to_interval(here_str, pre_str)
here_sec_r = here_str.split(":").map(&:to_r).then{ _1 * 60 + _2 }
pre_sec_r = pre_str.split(":").map(&:to_r).then{ _1 * 60 + _2 }
#差を求める
interval_sec_r = here_sec_r - pre_sec_r
#秒から分の単位を取得
# min = interval_sec_r / 60 (下記に修正)
#余りの秒を取得
# sec = interval_sec_r.modulo(60) (下記に修正)
# interval_str = "#{sprintf("%02d", min)}:#{sprintf("%2.2f", sec)}" (下記に修正)
# =====文字列に戻す部分の修正========
# Rationalから整商と剰余を配列で返すNumeric#divmodを使用
min, sec = interval_sec_r.divmod(60)
# String#%を使って成形
interval_str = "#{"%02d" % min}:#{"%05.2f" % sec}"
# =====//文字列に戻す部分の修正========
return interval_str
end
interval_str = to_interval("03:12.14", "01:30.24")
puts "A-B間のタイムは#{interval_str}です。"
#=> A-B間のタイムは01:41.90です。
秒をもとめるところが随分とスッキリしました。