列挙とは、定義上、「一定量の何かを1つずつ言及する行為」です。 プログラミングでは、言及する代わりに、モニターへの単純な出力であるか、何らかの要素の選択および/または変換の実行であるかを問わず、実行するアクションを選択します。
プログラミングでは、各ステップでチェーンに追加の変換関数を追加することにより、単位時間ごとにコレクションを選択して処理する多くの方法があります。 また、各ステップは、処理結果を次のステップに転送する前にコレクション全体を消費するか、コレクションの1つ以上の要素をすべての変換ステップに渡すことでコレクションを「怠”に」処理できます。
Ruby列挙の仕組み
この投稿では、ブロックと
yieldの機能の概要を簡単に説明します。 興味のあるRuby
のブロックは、メソッドまたは
proc /
lambda内で定義されたコードです。
yieldは、コードの別の
ブロックが別の場所から挿入される現在のコードのブロックと考えることができます。 見せてあげましょう。
def my_printer puts "Hello World!" end def thrice 3.times do yield end end thrice &method(:my_printer)
メソッドは、
yieldコマンドに対して2つの形式のブロックを取ります:procまたはblocks。
メソッドmethodは、
メソッド定義を
procに変換し、上記の
my_printerの例のように、
ブロックとして内部に渡すことができます。
上記の
yieldコマンドが記述されている場所では、ブロックとして渡されたコードが
yieldの代わりになるかのように、同等です。 したがって、最初のケースでは、
yield呼び出しが
puts "Hello World!"に置き換えられ、2番目の
yieldが
puts "Ruby"に置き換えられていること
を想像してください。
yieldは単純な列挙子としても機能します。
yieldの後に値
を追加すること
により 、
block /
procのパラメーターとして任意の値を内側に渡すことができます。
def simple_enum yield 4 yield 3 yield 2 yield 1 yield 0 end simple_enum do |value| puts value end
最小列挙子の要件
Rubyで列挙子を作成する標準的な方法は、
each 、yields / yields値です。 これを念頭に置いて、任意のRubyオブジェクトで
各メソッドを宣言し、
Enumerableモジュールからコレクションを処理および実行するための50を超えるメソッドのすべての利点を取得できます。 有効な
各メソッドを持つオブジェクト内
にEnumerableを追加するだけで、これらすべてのメソッドを完全に使用できます(Enumerableモジュールのメソッドを参照)。
列挙子は、
配列型
(配列)の単純なコレクションに限定されず、宣言された
各メソッドを持つコレクションで動作できます(通常、「先祖/先祖」にEnumerableモジュールがあります)。
Array.ancestors
「レイジー」列挙子ではなく「レイジー」列挙子
「遅延」列挙子は、通常、コレクションを処理するための最良の方法と見なされます。 必要な限り、無段階の無限シーケンスをバイパスできます。
人々がピザを収集し、各人がピザの準備/変換の1ステップのみを担当する組立ラインを想像してください。 最初の人は正しい形の生地を投げ、次の人はソース、次のチーズ、追加ごとに一人(ソーセージ、ピーマン、トマト)を加え、別の人はすべてをオーブンに入れ、最後の人は完成したピザをあなたに届けます。 この例では、Rubyビルドの「遅延」バージョンはピザの注文をいくつでも持つことになっていますが、残りのピザはすべて、最初のピザがすべての処理ステップ/ステップを完了するまで待ってから次のピザを作ります。
「遅延」列挙子を使用していない場合、各ステップ/ステージは、コレクション全体が単位時間ごとに1ステップ完了するまで待つことができます。 たとえば、ピザの注文が20件ある場合、生地を投げる人は、そのうちの1人がソースを追加する前に20個作る必要があります。 そして、ラインの各ステップは同様の方法で待機します。 現在、処理する必要のあるコレクションが多いほど、残りの組み立てラインを待機し続けるのはばかげているようです。
より重要な例:すべてのユーザーに送信する必要がある手紙のリストを処理します。 コードにエラーがあり、リスト全体が「怠iに」処理されない場合、誰もメールを受信しない可能性があります。 「怠yな」実行の場合、潜在的には、間違った郵便アドレスが問題/エラーを引き起こす前に、ほとんどのユーザーに手紙を送る可能性があります。 送信レコードに送信の成功ステータスが含まれている場合、エラーが発生したレコード(場所)を追跡する方が簡単です。
Rubyで「遅延」列挙子を作成するには、
Enumerableモジュールが含まれるオブジェクトで
lazyを呼び出すか、
各メソッドが宣言されたオブジェクトで
to_enum.lazyを呼び出す
だけです。
class Thing def each yield "winning" yield "not winning" end end a = Thing.new.to_enum.lazy Thing.include Enumerable b = Thing.new.lazy a.next
to_enum呼び出しは、 EnumeratorとEnumerableの両方であり、すべてのメソッドにアクセスできるオブジェクトを返します 。
列挙子のどのメソッドがコレクション全体を一度に「消費」し、どのメソッドがそれを「消費」(実行)-「怠lazに」するかに注意を払うことが重要です。 たとえば、
パーティション方式は一度にコレクション全体を消費するため、無限のコレクションには受け入れられません。 遅延実行のより良い選択は、
chunkまたは
selectなどのメソッドです。
x = (0..Flot::INFINITY) y = x.chunk(&:even?)
無限のコレクションで
selectを使用
する場合は、最初に
遅延メソッドを呼び出して、コレクション全体が
selectメソッドによって消費されないようにし、無限の実行によりプログラムを強制終了する必要があります。
遅延列挙子の作成
Rubyには
Enumerator :: Lazyクラス
があり 、Rubyでのような独自の列挙子メソッドを記述できます。
(0..Float::INFINITY).take(2)
良い例として、FizzBuzzを実装します。これは任意の番号で実行でき、無限のFizzBuzzの結果を得ることができます。
def divisible_by?(num) ->input{ (input % num).zero? } end def fizzbuzz_from(value) Enumerator::Lazy.new(value..Float::INFINITY) do |yielder, val| yielder << case val when divisible_by?(15) "FizzBuzz" when divisible_by?(3) "Fizz" when divisible_by?(5) "Buzz" else val end end end x = fizzbuzz_from(7)
Enumerator :: Lazyを使用すると、
yielderに何を
フィードするかは関係ありません。シーケンスの各ステップで値が返されます。
nextを使用すると、列挙子は現在の進行状況を監視します。 ただし、
nextを数回呼び出した後に
それぞれを呼び出すと、コレクションの最初から開始されます。
Enumerator :: Lazy.newに渡すパラメーターは、
列挙子を通過するコレクションです。
Enumerableまたは互換性のあるオブジェクトに対してこのメソッドを記述した場合、単に
selfをパラメーターとして渡すことができます。
valは、コレクションメソッド
eachによってユニットごとに生成される唯一の値になり、
yielderは、
それぞれの場合と
同様に、渡すコードブロックの唯一のエントリポイントになります。
高度な列挙子の使用
データコレクションを処理する場合、最初に制限フィルターを設定することをお勧めします。その後、コードを使用してデータを処理する時間を大幅に短縮できます。 データベースから処理用のデータを受け取った場合、可能であれば、データベースの内部言語で制限フィルターを設定してから、さらにデータをRubyに転送します。 そのため、はるかに効果的です。
require "prime" x = (0..34).lazy.select(&Prime.method(:prime?)) x.next
上記の
選択方法の後、他の方法をデータ処理に追加できます。 これらのメソッドは、すべての素数ではなく、素数内の限られたデータのみを処理します。
グルーピング
データを列に処理する優れた方法の1つは、
group_byを使用して、結果をグループの連想配列に変換することです。 その後、すべての結果に興味があるかのように結果をプルします。
[0,1,2,3,4,5,6,7,8].group_by.with_index {|_,index| index % 3 }.values
結果をWebページに表示する場合、データは次の順序で配置されます。
0 3 6 1 4 7 2 5 8
上記の
group_by呼び出しは、値とインデックスの両方をブロックに渡します。 配列の値にアンダースコアを使用して、この値に関心がなく、インデックスにのみ関心があることを示します。 結果として得られるのは、キー0、1、2がグループ化した値の各グループを指す連想配列です。 キーを心配する必要がないので、この連想配列で
値を呼び出して配列の配列を取得し、必要に応じてさらに表示します。
結果を列の形式で左から右に並べたい場合、次のようにします。
threes = (0..2).cycle [0,1,2,3,4,5,6,7,8].slice_when { threes.next == 2 }.to_a
threes列挙子は、「怠lazな」方法で0から2まで無限に移動します。 その結果、次の結論が得られます。
0 1 2 3 4 5 6 7 8
Rubyには、上記の結果をあるビューから別のビューに切り替える
転置メソッドもあります。
x = [[0, 1, 2], [3, 4, 5], [6, 7, 8]] x = x.transpose
「折りたたみ」
コレクションを結果にまとめる方法を見てみましょう。 他の言語では、これは通常
foldメソッドを介して行われます。 Rubyでは、これは
reduceと
injectで長い間行われてきました。 より最近の解決策、および
each_with_objectを使用してこれを行うための好ましい方法。 主なアイデアは、あるコレクションを別のコレクションに処理し、結果として機能することです。
整数の合計は次のように簡単です。
[1,2,3].reduce(:+)
each_with_objectは通常、更新可能なオブジェクトを必要とします。 整数オブジェクトをそれ自体から変更することはできません。このため、この簡単な例ではAddStoreオブジェクトを作成しました。
これらのメソッドは、あるコレクションからデータを取得して別のコレクションに入れると、作品でよりよく実証できます。
injectと
reduceはRubyの同じメソッドエイリアスであり、ブロックに基づいて列挙子を構築し続けるには、ブロックの最後にこの値を返す必要がある
ことに注意してください。
each_with_objectは、列挙子をさらに構築する要素を返すためにブロックの最後の部分を必要としません。
collection = [:a, 2, :p, :p, 6, 7, :l, :e] collection.reduce("") { |memo, value| memo << value.to_s if value.is_a? Symbol memo
構造
Rubyのオブジェクト構造も列挙オブジェクトであり、それらを使用して便利なオブジェクトを作成し、メソッドを記述することができます。
class Pair < Struct.new(:first, :second) def same?; inject(:eql?) end def add; inject(:+) end def subtract; inject(:-) end def multiply; inject(:*) end def divide; inject(:/) end def swap! members.zip(entries.reverse) {|a,b| self[a] = b} end end x = Pair.new(23, 42) x.same?
通常、構造は大規模なコレクションには使用されませんが、データをまとめて整理する方法として有用なデータオブジェクトとして使用されます。これにより、大きくなりすぎたデータではなく、データの透過的な使用が促進されます。
データの過剰成長は、2つ以上の変数が常にグループで使用される場合ですが、同時にそれらが別々に使用される場合は意味がありません。 この変数グループは、オブジェクト/クラスにグループ化する必要があります。
そのため、Rubyの構造は通常、小さなデータのコレクションですが、このデータだけでまったく異なるデータのコレクションを表すことができることを示唆するものはありません。 この場合、構造は同じデータコレクションに変換を実装する方法になる可能性があります。これは、独自のクラスを記述することで同じことを行う可能性が高いです。
まとめると
Rubyは、データコレクションの操作だけでなく作業も簡単にする非常に素晴らしい言語です。 Rubyが提供するもののすべてを調べることで、よりエレガントなコードを記述できるだけでなく、コードをテストおよび最適化してパフォーマンスを向上させることができます。
パフォーマンスが重要な場合は、個々の実装のパフォーマンスを測定し、もちろん、可能な場合はできるだけ早く、処理プロセスでフィルターと制限/制限を設定してください。
読み取りまたは
読み取り行ではなく、ファイルで
readlineメソッドを使用して、入力を小さなチャンクに制限する
か 、SQLで
LIMIT番号を使用することを検討してください。
遅延反復は、タスクを異なるスレッドまたはバックグラウンド処理ジョブに分割する際に非常に役立ちます。 「遅延」イテレーションの概念には実際に欠点がないので、どこでもコレクションを消費するように選択できます。 それは柔軟性を提供し、イテレータを備えたRustのようないくつかの言語は「怠lazに」実装されるという標準を採用しています。
データを操作および管理する方法に直面するとき、可能性は無限です。 そして、プログラミングでデータセットを操作するあらゆる方法を学び、作成するのは楽しいプロセスです。 Rubyには、
列挙可能な各メソッドのサンプルが十分に文書化されているため、それらから学ぶのに役立ちます。 プログラミングプロセスをより楽しく、より楽しくするのに役立つ多くの新しいことを実験し、発見することをサポートします。