Techioz Blog

Rubyでこのフィルタを実行して一緒に並べ替えるより良い方法はありますか?

概要

  1. 以下のような行があります
......, start Mon 10/30 10:08
......, start Thu 12/21 9:21
  1. やりたいこと: 日付と時刻で行を並べ替えますが、今日から始まる行を削除したいです。

  2. 以下は、この関数を実装するための Ruby コードです。

time = Time.new

$mon = time.month
$mday = time.day

# ......

array_tmp = results_all.lines.reject do |x|
    times = x.split(/,/)[1].scan(/\d+/).map(&:to_i)
    times[0] == $mon &&  times[1] == $mday
end

array_tmp.sort_by {|x| x.split(/,/)[1].scan(/\d+/).map(&:to_i)}]

私の質問は次のとおりです。

Rubyでこのフィルタとソートを同時に実行する、より優れたエレガントな方法はありますか?

解決策

次のように与えられたとします。

arr = [
  "has , start Mon 10/30 10:08",
  "dog , start Thu 9/24 4:08",4
  "fleas , start Thu 12/21 9:21",
  "Saffi , start Thu 10/29 19:33",
  "My , start Thu 9/7 9:54"
]
today = Date.today
  #=> #<Date: 2023-12-21 ((2460300j,0s,0n),+0s,2299161j)>

次に、次のように書くことができます。

RGX = /
      \d{1,2}   # match 1 or 2 digits
      \/        # match a forward slash
      \d{1,2}   # match 1 or 2 digits
      [ ]+      # match 1 or more spaces
      \d{1,2}   # match 1 or 2 digits
      :         # match a colon
      \d{2}     # match two digits
      $         # match the end of the string
      /x        # invoke free-spacing regex definition mode
arr.filter_map do |s|
  dt = string_to_datetime(s)
  [dt, s] unless dt.to_date == today 
end.sort.map(&:last)
  #=> ["My , start Thu 9/7 9:54",
  #    "dog , start Thu 9/24 4:08",
  #    "Saffi , start Thu 10/29 19:33",
  #    "has , start Mon 10/30 10:08"]
def string_to_datetime(str)
  DateTime.strptime(str[RGX], '%m/%d %H:%M')
end

途中の計算

arr.filter_map do |s|
  dt = string_to_datetime(s)
  [dt, s] unless dt.to_date == today 
end
  #=> [[#<DateTime: 2023-10-30T10:08:00+00:00 ((2460248j,36480s,0n),+0s,2299161j)>,
  #     "has , start Mon 10/30 10:08"],
  #    [#<DateTime: 2023-09-24T04:08:00+00:00 ((2460212j,14880s,0n),+0s,2299161j)>,
  #     "dog , start Thu 9/24 4:08"],
  #    [#<DateTime: 2023-10-29T19:33:00+00:00 ((2460247j,70380s,0n),+0s,2299161j)>,
  #     "Saffi , start Thu 10/29 19:33"],
  #    [#<DateTime: 2023-09-07T09:54:00+00:00 ((2460195j,35640s,0n),+0s,2299161j)>,
  #     "My , start Thu 9/7 9:54"]]

ソート中の配列を表示します。各要素は 2 要素の配列です。 2 番目の要素は、並べ替えられる文字列の 1 つです。 1 つ目は、その文字列の月-日-時刻表現から計算された DateTime インスタンスです。並べ替えは主に最初の要素で行われ、2 番目の要素は 2 つの DateTime インスタンスが等しい場合に結合を解除するためにのみ使用されます。ドキュメント「配列#sort」を参照してください。

あるいは次のように書くこともできます。

arr.reject do |s|
  string_to_datetime(s).to_date == today 
end.sort_by { |s| string_to_datetime(s) }
  #=> ["My , start Thu 9/7 9:54",
  #    "dog , start Thu 9/24 4:08",
  #    "Saffi , start Thu 10/29 19:33",
  #    "has , start Mon 10/30 10:08"]

これは、最初の方法よりもいくらか高速であることが期待できます (string_to_datetime が 2 回呼び出されても、sort_by のブロックによって行われる計算は 1 回だけ行われるため)。 Enumerable#sort_by を参照してください。

Date::today、Enumerable#filter_map、DateTime::strptime、String#[]、および DateTime#to_date も参照してください。 DateTime::strptime で使用されるフォーマット ディレクティブについては、Time#strftime を参照してください。