1601


例えば、フィボナッチ数列`(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
987, 1597, 2584, 4181, 6765, 10946 ..)`を作るようなクラスを作りたいとして、下のようなコードを書いたとします。

[code]

     class Fibonacci
       def initialize(n)
         @n = n
         @f1 = 1
         @f0 = 0
       end
    
       def to_a
         (0..(@n-1)).map {|t| fib t }
       end
    
       def fib t
         case t
         when 0..1
           t
         else
           now = @f1 + @f0
           @f0 = @f1
           @f1 = now
         end
       end
       private :fib
     end
    
[/code]

このとき、このコードは下のように実行することができるかと思われます。

[code]

     Fibonacci.new(10).to_a.each_with_index do |result, index|
       puts "#{index + 1}番目: #{result}"
     end
    
[/code]

とはいえ、わざわざ`to_a`を呼び出して、一度`Array`にしてから、`each_with_index`を呼び出すのは非常に冗長のように感じます。むしろこのオブジェクトに対して、直接`each_with_index`(あるいは`each`)を呼び出せたほうが簡潔になるように感じます。例えば下のようにです。

[code]

     Fibonacci.new(10).each_with_index do |result, index|
       puts "#{index + 1}番目: #{result}"
     end
    
[/code]

このようにするためには、このクラスにはどのようなコードを追加してあげればいいのでしょうか。同様に、`map`なども共通で実装できれば、よりベターです。


CC BY-SA 3.0
2014-12-14T15:49:49.750
1593
2014-12-16T11:17:27.300
939
question
ruby
Rubyで簡潔にeachなどの機能を持つクラスを実装する方法


Enumerableをincludeして、eachメソッドを実装すれば可能です。

実装部分は手抜きですが、例えば以下の様にすれば `to_a`無しで `each_with_index` を呼び出すことができるようになります。

[code]

    require 'enumerator'
    
    class Fibonacci
      include Enumerable
      def initialize(n)
        @n = n
        @f1 = 1
        @f0 = 0
      end
    
      def to_a
        (0..(@n-1)).map {|t| fib t }
      end
    
      # 追加
      def each
        to_a.each do |x|
          yield x
        end
      end
    
      def fib t
        case t
          when 0..1
            t
          else
            now = @f1 + @f0
            @f0 = @f1
            @f1 = now
        end
      end
      private :fib
    
    end
    
[/code]

このeachの実装は簡単のために`to_a`の呼び出しをクラス側で肩代わりしているだけです。 これによって、以下の様に呼び出すことができます。

[code]

    Fibonacci.new(10).each_with_index do |result, index|
       puts "#{index + 1}番目: #{result}"
    end
    
[/code]

map も可能です。

[code]

    puts Fibonacci.new(10).map {|x| x + 10}
    
[/code]


CC BY-SA 3.0
2014-12-14T17:19:47.400
1595
2014-12-14T17:27:34.033
2014-12-14T17:27:34.033
728
728
1593
answer


`Enumerable` を `include` して `each` を実装すればよいです。

<http://docs.ruby-lang.org/ja/2.1.0/class/Enumerable.html>

> 要約
>
> 繰り返しを行なうクラスのための Mix-in。このモジュールの メソッドは全て each を用いて定義されているので、インクルード するクラスには
> each が定義されていなければなりません。

実装のポイントは

  * `Enumerable` を `include` する
  * `Enumerable` に必要な `each` を実装する

ロジックのポイントは、列挙するという特質から

  * `yield` を使うと `each` をうまく実装できることが多い
  * メモ化するとリソースがうまく節約できることが多い

その上で複数回の `each` の呼び出しなどを考慮すると以下のようなコードが妥当そうです。

[code]

    require 'enumerator'
    
    class Fibonacci
      include Enumerable
    
      def initialize(n)
        @n = n
        @fib = {1 => 0, 2 => 1}
      end
    
      def each
        1.upto @n do |i|
          yield fib i
        end
      end
    
      private
      def fib(n)
        return @fib[n] if @fib[n]
        @fib[n] = fib(n - 1) + fib(n - 2)
      end
    end
    
    
    if __FILE__ == $0 then
      f = Fibonacci.new 10
      f.each_with_index do |n, i|
        puts "#{i + 1}: #{n}"
      end
      puts f.map {|x| x + 10}
    end
    
[/code]

`Enumerable` を `include` して `each` を定義していれば、 `map` などのほか `include?`
なども使えるようになります。

<http://docs.ruby-lang.org/ja/2.1.0/class/Enumerable.html>

> インスタンスメソッド
>
> all? any? chunk collect map collect_concat flat_map count cycle detect find
> drop drop_while each_cons each_entry each_slice each_with_index
> each_with_object entries to_a find_all select find_index first grep group_by
> include? member? inject reduce lazy max max_by min min_by minmax minmax_by
> none? one? partition reject reverse_each slice_before sort sort_by take
> take_while to_h zip


CC BY-SA 3.0
2014-12-15T10:38:14.483
1601
2014-12-15T10:51:04.000
2014-12-15T10:51:04.000
982
982
1593
answer


私なら以下のような `Enumerator` を使った実装にします。

[code]

    def fib
      Enumerator.new do |yielder|
        x, y = 1, 1
        loop do
          yielder.yield x
          tmp = x + y
          x = y
          y = tmp
        end
      end.lazy
    end
    
    fib.each do |n|
      puts n
    end
    
[/code]

いわゆるジェネレータ的な実装だと思います。 遅延評価なので、無限リストとして扱えます。

個人的には UNIX ツールを作るのに便利なので良く使います。

<https://github.com/fukayatsu/json2yaml/blob/master/lib/json2yaml.rb>


CC BY-SA 3.0
2014-12-16T03:42:41.077
1654
2014-12-16T11:17:27.300
2014-12-16T11:17:27.300
3025
3025
1593
answer