C#のLINQの関数であるSkip()
, SkipWhile()
の使い方についてです。
配列やリストなどのシーケンスの指定した要素以降の要素群を取得することが出来ます。
この記事には.NET Framework 4.6.1を使用しています。
指定した数以降の要素がほしい
この配列の最初の方の要素はいらないなぁ。
最初の方の要素だけ削除して、残りの要素だけ取得する方法はないだろうか。
そんなときにはLINQのSkip()
がありますぜ。
public static IEnumerable<TSource> Skip<TSource>( this IEnumerable<TSource> source, int count );
Enumerable.Skip(TSource) メソッド (IEnumerable(TSource), Int32) (System.Linq)
引数に、先頭からいらない要素の数を指定すれば、その数以降の要素のシーケンスを返してくれます。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 0 ~ 9 の数値データ(Range()で作った方が早いけどね!) int[] numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // 日曜日から土曜日までの文字列データ string[] texts = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // 最初の3個の要素は飛ばして、残りの要素を取得するよ。 IEnumerable<int> skippedNumbers = numbers.Skip( 3 ); // 最初の4個の要素は飛ばして、残りの要素を取得するよ IEnumerable<string> skippedTexts = texts.Skip( 4 ); // 結果発表 System.Console.WriteLine( "skippedNumbers:{0}", skippedNumbers.Text() ); System.Console.WriteLine( "skippedTexts:{0}", skippedTexts.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
skippedNumbers:[3], [4], [5], [6], [7], [8], [9],
skippedTexts:[Thu], [Fri], [Sat],
この指定する引数には、要素の数より大きい数値や負数を渡してもきちんと動作します。
負数を指定しなくちゃいけない状況が謎ではあるけど……。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 0 ~ 9 の数値データ(Range()で作った方が早いけどね!) int[] numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // 日曜日から土曜日までの文字列データ string[] texts = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // 明らかに、データ数より多い数をスキップする暴挙! IEnumerable<int> skippedNumbers = numbers.Skip( 100 ); // 負数を渡すという、サイコパス的な行動! IEnumerable<string> skippedTexts = texts.Skip( -5 ); // 結果発表 System.Console.WriteLine( "skippedNumbers:{0}", skippedNumbers.Text() ); System.Console.WriteLine( "skippedTexts:{0}", skippedTexts.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
skippedNumbers:
skippedTexts:[Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat],
指定した条件以降の要素がほしい
先ほどのSkip()
は、要素の数を指定してスキップしていました。
ただこのスキップする条件を要素の数ではなく、何かしら特定の条件を指定したい場合もあると思います。
そんな場合はSkipWhile()
。
引数にスキップする条件を記述できます。
public static IEnumerable<TSource> SkipWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate );
Enumerable.SkipWhile(TSource) メソッド (IEnumerable(TSource), Func(TSource, Boolean)) (System.Linq)
記述した条件を満たす限り、要素をスキップし続けます。
以下の例では、ラムダ式を用いて条件文を記述しています。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 0 ~ 9 の数値データ(Range()で作った方が早いけどね!) int[] numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // 日曜日から土曜日までの文字列データ string[] texts = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // 7未満の数値なら要素をスキップ IEnumerable<int> skippedNumbers = numbers.SkipWhile( value => value < 7 ); // 最後の文字が"n"なら要素をスキップ IEnumerable<string> skippedTexts = texts.SkipWhile( value => value.EndsWith( "n" ) ); // 結果発表 System.Console.WriteLine( "skippedNumbers:{0}", skippedNumbers.Text() ); System.Console.WriteLine( "skippedTexts:{0}", skippedTexts.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
skippedNumbers:[7], [8], [9],
skippedTexts:[Tue], [Wed], [Thu], [Fri], [Sat],
要素のインデックスを取得できちゃう
SkipWhile()
ではスキップする条件を指定する際に、各要素の情報だけでなく、各要素のインデックスも取得することができます。
public static IEnumerable<TSource> SkipWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate );
Enumerable.SkipWhile(TSource) メソッド (IEnumerable(TSource), Func(TSource, Int32, Boolean)) (System.Linq)
条件を記述する関数の引数にint
型の引数を追加すればいいだけです。
以下の例ではラムダ式の条件文にて、index
の引数でインデックスの番号を渡しています。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 0 ~ 9 の"降順"数値データ int[] numbers = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; // 日曜日から土曜日までの文字列データ string[] texts = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // 数値と各要素のインデックスをかけたものが、20未満ならスキップ IEnumerable<int> skippedNumbers = numbers.SkipWhile( ( value, index ) => value * index < 20 ); // 文字数が各要素のインデックスより、大きければスキップ IEnumerable<string> skippedTexts = texts.SkipWhile( ( value, index ) => value.Length > index ); // 結果発表 System.Console.WriteLine( "skippedNumbers:{0}", skippedNumbers.Text() ); System.Console.WriteLine( "skippedTexts:{0}", skippedTexts.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
skippedNumbers:[5], [4], [3], [2], [1], [0],
skippedTexts:[Wed], [Thu], [Fri], [Sat],
Where()との違い
さて、似たようなLINQの関数としてWhere()
というものがあります。
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate );
Enumerable.Where(TSource) メソッド (IEnumerable(TSource), Func(TSource, Boolean)) (System.Linq)
Where()
は条件と同じ要素を取得する関数です。
このWhere()
とSkipWhile()
を比べてみましょう。
以下の例では、指定した数値以下の要素を省く条件を記述しています。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 0 ~ 9 の"昇順"の数値データ int[] numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // 数値が5未満ならスキップ IEnumerable<int> skippedNumbers = numbers.SkipWhile( value => value < 5 ); // 数値が5未満をフィルタリング IEnumerable<int> whereNumbers = numbers.Where( value => value >= 5 ); // 結果発表 System.Console.WriteLine( "skippedNumbers:{0}", skippedNumbers.Text() ); System.Console.WriteLine( "whereNumbers :{0}", whereNumbers.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
skippedNumbers:[5], [6], [7], [8], [9],
whereNumbers :[5], [6], [7], [8], [9],
同じ結果になりました。
先ほどはデータとなる数値の並びが昇順に並んでいる、整理されたデータを使っていました。
ではこのデータの並びをばらばらにして、再度検証してみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 0 ~ 9 の"バラバラ"の数値データ int[] numbers = new int[] { 0, 3, 6, 1, 4, 5, 8, 7, 2, 9 }; // 数値が5未満ならスキップ IEnumerable<int> skippedNumbers = numbers.SkipWhile( value => value < 5 ); // 数値が5未満をフィルタリング IEnumerable<int> whereNumbers = numbers.Where( value => value >= 5 ); // 結果発表 System.Console.WriteLine( "skippedNumbers:{0}", skippedNumbers.Text() ); System.Console.WriteLine( "whereNumbers :{0}", whereNumbers.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
skippedNumbers:[6], [1], [4], [5], [8], [7], [2], [9],
whereNumbers :[6], [5], [8], [7], [9],
結果が変わりました。
Where()
では、全ての要素に対して条件にあっている要素を省き、それ以外の要素を返しています。
SkipWhile()
では、先頭から条件をあっている要素を省き、条件にあった要素以降の要素を返しています。
そのため、Where()
の結果では5未満の要素は存在しません。
ですが、SkipWhile()
の結果では5未満の要素は存在します。先頭のほうに5以上の要素があれば、それ以降の要素は取得されるからです。
このあたりに気をつけて、Skip()
とSkipWhile()
を七兆回ほど使ってみてください。
LINQのリンク
LINQ一覧
www.urablog.xyzTake, TakeWhile
指定した条件までの要素を取得したい!
www.urablog.xyz