Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyの魔法の列挙可能なモジュール

Ruby Magicの別のエピソードの時間です!今回は、Rubyの最も魅力的な機能の1つを見ていきます。これは、 ArrayなどのRubyの列挙可能なクラスを操作するときに使用するメソッドのほとんどを提供します。 、ハッシュ およびRange 。このプロセスでは、列挙可能なオブジェクトで何ができるか、列挙がどのように機能するか、単一のメソッドを実装してオブジェクトを列挙可能にする方法を学習します。

列挙可能 #each およびEnumerator

列挙 オブジェクトをトラバースすることを指します。 Rubyでは、オブジェクトを列挙可能と呼びます。 アイテムのセットと、各アイテムをループする方法について説明している場合。

組み込みの列挙型は、列挙型を含めることで列挙機能を取得します モジュール。#include?などのメソッドを提供します。 、 #count #map #select および#uniq 、 とりわけ。配列とハッシュに関連付けられているメソッドのほとんどは、実際にはこれらのクラス自体には実装されておらず、含まれています。

#countなどのいくつかのメソッド および#take Arrayで クラス、 Enumerable の配列を使用する代わりに、配列用に特別に実装されています モジュール。これは通常、操作を高速化するために行われます。

列挙可能 モジュールは#eachという名前のメソッドに依存しています 、これは、含まれているすべてのクラスに実装する必要があります。配列上のブロックで呼び出されると、 #each メソッドは、配列の各要素のブロックを実行します。

irb> [1,2,3].each { |i| puts "* #{i}" }
* 1
* 2
* 3
=> [1,2,3]

#eachと呼ぶと 配列のメソッドなし 要素ごとに実行するブロックを渡すと、 Enumeratorのインスタンスを受け取ります。 。

irb> [1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>

列挙子のインスタンス オブジェクトを反復処理する方法を説明します。列挙子はオブジェクトを手動で反復し、列挙をチェーンします。

irb> %w(dog cat mouse).each.with_index { |a, i| puts "#{a} is at position #{i}" }
dog is at position 0
cat is at position 1
mouse is at position 2
=> ["dog", "cat", "mouse"]

#with_index メソッドは、変更された列挙子がどのように機能するかを示す良い例です。この例では、 #each 列挙子を返すために配列で呼び出されます。次に、 #with_index 配列の各要素にインデックスを追加して、各要素のインデックスを印刷できるようにするために呼び出されます。

オブジェクトを列挙可能にする

内部的には、 #maxのようなメソッド 、 #map および#take #eachに依存する 機能する方法。

def max
  max = nil
 
  each do |item|
    if !max || item > max
      max = item
    end
  end
 
  max
end

内部的には、列挙可能 のメソッドにはCの実装がありますが、上記の例は #maxの方法を大まかに示しています。 動作します。 #eachを使用する すべての値をループして最高値を記憶すると、最大値が返されます。

def map(&block)
  new_list = []
 
  each do |item|
    new_list << block.call(item)
  end
 
  new_list
end

#map 関数は、渡されたブロックを各アイテムで呼び出し、結果を新しいリストに入れて、すべての値をループした後に返します。

Enumerableのすべてのメソッド以降 #eachを使用する ある程度の方法では、カスタムクラスを列挙可能にするための最初のステップは、 #eachを実装することです。 メソッド。

#eachの実装

#eachを実装する 関数とEnumerableを含む クラス内のモジュール、それは列挙可能になり、 #minのようなメソッドを受け取ります 、 #take および#inject 無料です。

ほとんどの状況では、配列などの既存のオブジェクトにフォールバックして、 #eachを呼び出すことができます。 その方法については、最初から自分で作成する必要がある例を見てみましょう。この例では、 #eachを実装します リンクリスト 列挙可能にするため。

リンクリスト:配列のないリスト

リンクリストはデータ要素のコレクションであり、各要素は次の要素を指します。リストの各要素には、 headという名前の2つの値があります。 とテール 。ヘッドは要素の値を保持し、テールはリストの残りの部分へのリンクです。

[42, [12, [73, nil]]

3つの値(42、12、および73)を持つリンクリストの場合、最初の要素のヘッドは42であり、テールは2番目の要素へのリンクです。 2番目の要素の頭は12で、尾は3番目の要素を保持します。 3番目の要素の頭は73で、尾は nilです。 、これはリストの終わりを示します。

Rubyでは、 @headという名前の2つのインスタンス変数を保持するクラスを使用してリンクリストを作成できます。 および@tail

class LinkedList
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
end

#<< メソッドは、リストに新しい値を追加するために使用されます。これは、渡された値を先頭、前のリストを末尾として新しいリストを返すことで機能します。

この例では、 #inspect メソッドが追加されたので、リストを調べて、含まれている要素を確認できます。

irb> LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]

リンクリストができたので、 #eachを実装しましょう。 その上に。 #each 関数はブロックを受け取り、オブジェクトの値ごとにそれを実行します。リンクリストに実装する場合、リストの @head で渡されたブロックを呼び出すことにより、リストの再帰的な性質を利用できます。 、 #eachを呼び出します @tailに 、存在する場合。

class LinkedList
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
 
  def each(&block)
    block.call(@head)
    @tail.each(&block) if @tail
  end
end

#eachを呼び出す場合 リンクリストのインスタンスでは、渡されたブロックを現在の @headで呼び出します。 。次に、 @tailのリンクリストでそれぞれを呼び出します テールがnilでない限り 。

irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each { |item| puts item }
42
12
73
=> nil

リンクリストが#eachに応答するようになりました 、 Enumberableを含めることができます リストを列挙可能にするため。

class LinkedList
  include Enumerable
 
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
 
  def each(&block)
    block.call(@head)
    @tail.each(&block) if @tail
  end
end
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.count
=> 3
irb> list.max
=> 73
irb> list.map { |item| item * item }
=> [1764, 144, 5329]
irb> list.select(&:even?)
=> [42, 12]

列挙子を返す インスタンス

リンクリスト内のすべての値をループできるようになりましたが、列挙可能な関数をチェーンすることはまだできません。そのためには、列挙子を返す必要があります #eachのインスタンス 関数はブロックなしで呼び出されます。

class LinkedList
  include Enumerable
 
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
 
  def each(&block)
    if block_given?
      block.call(@head)
      @tail.each(&block) if @tail
    else
      to_enum(:each)
    end
  end
end

オブジェクトを列挙型でラップするには、 #to_enumを呼び出します。 その上でメソッド。 :eachを渡します 、それは列挙子が内部で使用する必要がある方法です。

次に、 #eachを呼び出します ブロックのないメソッドを使用すると、列挙を連鎖させることができます。

irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each
=> #<Enumerator: [42, [12, [73, nil]]]:each>
irb> list.map.with_index.to_h
=> {42=>0, 12=>1, 73=>2}

9行のコードとインクルード

#eachを実装する Enumerableを使用する モジュールと戻り値Enumerator 独自のオブジェクトから、9行のコードとインクルードを追加することでリンクリストを強化することができました。

これで、Rubyの列挙型の概要は終わりです。この記事についてのご意見、またはご不明な点がございましたら、お気軽にお問い合わせください。私たちは常に調査と説明を行うトピックを探しています。Rubyに何か魔法のようなものがあれば、遠慮なく@AppSignalまでお問い合わせください。


  1. Ruby2.6の9つの新機能

    Rubyの新しいバージョンには、新しい機能とパフォーマンスの改善が含まれています。 変更についていきますか? 見てみましょう! 無限の範囲 Ruby 2.5以前のバージョンは、すでに1つの形式の無限範囲をサポートしています( Float ::INFINITY を使用) )、しかしRuby2.6はこれを次のレベルに引き上げます。 新しい無限の範囲 次のようになります: (1..) これは、(1..10)のような終了値がないため、通常の範囲とは異なります。 。 使用例 : [a, b, c].zip(1..) # [[a, 1], [b, 2], [c, 3]] [1,2,3,

  2. Ruby Enumerable Moduleの基本ガイド(+私のお気に入りの方法)

    列挙可能とは何ですか? 列挙可能は反復法のコレクションです 、Rubyモジュール、そしてRubyを優れたプログラミング言語にする大きな部分です。 列挙可能には次のような便利なメソッドが含まれます : マップ 選択 注入 列挙可能なメソッドは、ブロックを与えることで機能します。 そのブロックで、すべての要素で何をしたいのかを伝えます。 例 : [1,2,3].map { |n| n * 2 } すべての数値が2倍になった新しい配列を提供します。 正確に何が起こるかは、使用する方法、 mapによって異なります。 すべての値を変換するのに役立ちます、 select リス