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

RubyのEnumerableを自在に分割する!slice_before・slice_after・slice_whenの使い方

今回はslice_beforeslice_whenslice_afterについて解説します。これらは、配列などのEnumerable(列挙可能なオブジェクト)に含まれる要素を、任意の条件に基づいてグループ化したいときに非常に便利なメソッドです。

Array#sliceをご存じの方も多いでしょう。インデックスの範囲を指定して、配列の一部を取り出すことができます。

a = ["a", "b", "c"]
a.slice(1, 2)
# => ["b", "c"]

これは便利ですが、Enumerableには使えません。というのも、Enumerableにはインデックスが存在しないからです。

一方、slice_beforeslice_whenslice_afterはインデックスに依存しないため、あらゆるEnumerableに対して使用できます。それでは見ていきましょう!

Enumerable#slice_beforeの使い方

Enumerable#slice_beforeは、条件にマッチした要素の「直前」でEnumerableを分割し、グループ化します。

マッチングには===演算子が使われるため、さまざまな種類の条件でマッチさせることができます。

値によるマッチ

単一の値でマッチさせることもできます。これは直感的ですね :)

a = ["a", "b", "c"]
a.slice_before("b").to_a
# => [["a"], ["b", "c"]]

正規表現によるマッチ

より複雑なテキストマッチには正規表現を使えます。

a = ["000", "b", "999"]
a.slice_before(/[a-z]/).to_a
# => [["000"], ["b", "999"]]

範囲(Range)によるマッチ

数値を扱う場合は、範囲を指定して配列を分割できます。

a = [100, 200, 300]
a.slice_before(150..250).to_a
# => [[100], [200, 300]]

クラスによるマッチ

少し意外に感じるかもしれませんが、これも===演算子の挙動として完全に正当な使い方です。

a = [1, "200", 1.3]
a.slice_before(String).to_a
# => [[1], ["200", 1.3]]

ブロックを使う

上記のどの方法でも柔軟性が足りない場合は、ブロックを使ってプログラム的にマッチ条件を記述できます。

a = [1, 2, 3, 4, 5]
a.slice_before do |item|
  item % 2 == 0
end.to_a
# => [[1], [2, 3], [4, 5]]

Enumerable#slice_afterの使い方

Enumerable#slice_afterEnumerable#slice_beforeとまったく同じように動作しますが、唯一の違いは、マッチした要素の「直後」で分割される点です。その名の通りですね :-)

a = ["a", "b", "c"]
a.slice_after("b").to_a
# => [["a", "b"], ["c"]]

もちろん、正規表現や範囲、ブロックを使ったマッチも可能です。ここでは例が冗長になるため割愛します。

Enumerable#slice_whenの使い方

Enumerable#slice_whenは、slice_beforeslice_afterとは少し毛色の違うメソッドです。配列内の単一の要素ではなく、「隣り合う2つの要素」のペアに対してマッチ判定を行います。

つまり、要素間の「境界」に着目してグループ化できるのです。

例えば、以下の例では隣接する要素との「近さ」に基づいてグループ化しています。

a = [1, 2, 3, 100, 101, 102]

# 隣接する2つの要素の差が10より大きい場合に
# 新しいグループを作成する。
a.slice_when do |x, y| 
  (y - x) > 10
end.to_a
# => [[1, 2, 3], [100, 101, 102]]

さらに詳しく知りたい方は、Ruby公式ドキュメントのslice_whenのページをチェックしてみてください。優れたコード例がいくつも掲載されています。

配列 vs Enumerable

これまでの例では説明をわかりやすくするために主に配列を使用しましたが、slice_beforeslice_whenslice_afterは任意のEnumerableに対して使えることを覚えておいてください。

例えば、大量のメールが格納されたファイル(mbox形式)から個々のメールを取り出す場合にも、slice_beforeが活躍します。以下のコードは公式ドキュメントからの引用です。

open("mbox") { |f|
  f.slice_before { |line|
    line.start_with?("From ")
  }.each { |mail|
    puts mail
  }
}

そして忘れてはならないのが、slice系メソッドの戻り値は配列ではなくEnumeratorであるという点です。つまり、mapeachなど、お気に入りのEnumerableメソッドをそのまま適用できます。さらに別の分割処理につなげることさえ可能です :)


  1. RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

    リンティング(Lint)とは、ソースコードに含まれるプログラム上の誤りやスタイル違反を自動的に検出する仕組みです。このチェックは「リンター」と呼ばれる静的コード解析ツールによって行われます。一方、コードフォーマッターは、あらかじめ設定されたルールセットにソースコードを厳密に準拠させることに特化したツールです。一般的にリンターは違反を報告するだけで、実際の修正はプログラマーの手に委ねられます。それに対してコードフォーマッターは、ルールを直接ソースコードに適用し、フォーマットのミスを自動的に修正してくれます。 プロジェクトで一貫性のあるコードスタイルを実現するには、通常リンティング用とフォーマッ

  2. Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

    Rubyでのログ出力入門:LoggerとLogrageの使い方 ロギングは、アプリケーション開発において最も重要なタスクの一つです。ログは以下のような場面で活用されます。 アプリ内部で何が起きているかを把握したいとき アプリケーションを監視したいとき 特定のデータに関するメトリクスを収集したいとき 新しいプログラミング言語を学ぶ際、最初に選ばれるのはその言語がネイティブに備えているロギング機構でしょう。標準機能は通常、扱いやすく、ドキュメントも充実しており、コミュニティでも広く使われています。 ただし、ログデータの内容や扱い方は、企業の方針、ビジネスの性質、アプリケーションの種類に