This page looks best with JavaScript enabled
⚠️

【Ruby】Enumeratorユースケース / 遅延評価で配列を取得する

 ·  ☕ 2 分で読めます
✏️

Enumeratorについて、なんとなく概念としてわかるようなわからないような……という感じだったので、ユースケースのサンプルとして記録しておく。
これで今後Enumeratorチャンスが来たときに「よし、Enumeratorで実装するぞ!」と思えるように。

ざっくり言うと、配列を遅延評価したいときに使うとよさそう。その中の一例として、評価前のロジックで無限ループになる場合を挙げます。
ちなみにコードはSetさんのものを参考にしました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'active_support/all'

class Event
  def initialize(first_time, interval)
    @first_time = first_time
    @interval = interval
  end

  def all
    Enumerator.new do |y|
      time = @first_time
      loop do
        y << time
        time += @interval
      end
    end
  end

  def duration(duration)
    all.take_while do |time|
      duration.cover?(time)
    end
  end
end

loopの無限ループをEnumeratorに突っ込む。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
irb(main):029:0> event =  Event.new(Time.now, 1.day)

irb(main):030:0>event.all
=> #<Enumerator: #<Enumerator::Generator:0x00007fa6b4119790>:each>

irb(main):031:0> event.all.first
=> 2022-12-01 22:03:03.299831 +0900

irb(main):033:0> event.all.take(3)
=> [2022-12-01 22:03:03.299831 +0900, 2022-12-02 22:03:03.299831 +0900, 2022-12-03 22:03:03.299831 +0900]

irb(main):034:0> event.duration(1.day.ago..5.day.since)
=> [2022-12-01 22:03:03.299831 +0900, 2022-12-02 22:03:03.299831 +0900, 2022-12-03 22:03:03.299831 +0900, 2022-12-04 22:03:03.299831 +0900, 2022-12-05 22:03:03.299831 +0900, 2022-12-06 22:03:03.299831 +0900]

irb(main):044:0> all = event.all
irb(main):045:0> all.next
=> 2022-12-01 22:03:03.299831 +0900
irb(main):046:0> all.next
=> 2022-12-02 22:03:03.299831 +0900
irb(main):047:0> all.next
=> 2022-12-03 22:03:03.299831 +0900

take_whileを使うと遅延評価で値を返すことができる。

Enumerable オブジェクトの要素を順に偽になるまでブロックで評価します。最初に偽になった要素の手前の要素までを配列として返します。
Enumerable#take_while (Ruby 3.1 リファレンスマニュアル)

もちろん、Enumeratorを使うからといって無限ループにしないといけないわけではない。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Event
  def initialize(first_time, interval)
    @first_time = first_time
    @interval = interval
  end

  def all
    Enumerator.new do |y|
      time = @first_time
      5.times do
        y << time
        time += @interval
      end
    end
  end
end
1
2
3
4
5
6
7
irb(main):028:0> event =  Event.new(Time.now, 1.day)
irb(main):029:0> event.all
=> #<Enumerator: #<Enumerator::Generator:0x00007fdcc10c47c0>:each>
irb(main):030:0> event.all.count
=> 5
irb(main):031:0> event.all.to_a
=> [2022-12-01 22:11:30.181606 +0900, 2022-12-02 22:11:30.181606 +0900, 2022-12-03 22:11:30.181606 +0900, 2022-12-04 22:11:30.181606 +0900, 2022-12-05 22:11:30.181606 +0900]

たった今思いついたのですが、数列系もEnumeratorチャンスな気がします。

その他参考になる

Ruby の Enumerator とたわむれる | Money Forward Money Forward Engineers&rsquo; Blog

Share on

END
END
@aiandrox

 
目次