次の文字列配列があると仮定します。
string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"}
LINQ を使用して、1 つの文字列に一致するインデックスのリストを取得することは可能ですか?
例として、文字列「avg」を検索して、次を含むリストを取得したいと思います
2、4
"avg" が str[2] と str[4] にあることを意味します。
.Select
インデックスを生成するめったに使用されないオーバーロードがあります。次のように使用できます。
str.Select((s, i) => new {i, s})
.Where(t => t.s == "avg")
.Select(t => t.i)
.ToList()
結果は、2 と 4 を含むリストになります。
次のように実行できます。
str.Select((v,i) => new {Index = i, Value = v}) // Pair up values and indexes
.Where(p => p.Value == "avg") // Do the filtering
.Select(p => p.Index); // Keep the index and drop the value
重要なステップは、ファンクターに現在のインデックスを提供するオーバーロードを使用することです。Select
まず、コードは実際にはリストを 2 回反復するのではなく、1 回だけ反復します。
そうは言っても、あなたの Select は実際にはすべてのインデックスのシーケンスを取得しているだけです。これは、Enumerable.Range を使用するとより簡単に実行できます。
var result = Enumerable.Range(0, str.Count)
.Where(i => str[i] == "avg")
.ToList();
リストが実際に 2 回繰り返されない理由を理解するには、慣れる必要があります。基本的な説明をしようと思います。
Select や Where など、ほとんどの LINQ メソッドはパイプラインと考える必要があります。各メソッドは、いくつかの小さな作業を行います。Select の場合、メソッドを指定すると、基本的には、「誰かが次のアイテムを要求するたびに、まず入力シーケンスにアイテムを要求し、次に必要なメソッドを使用してそれを別のものに変換します。そして、私を使っている人にそのアイテムを渡してください。」多かれ少なかれ、「誰かが私にアイテムを要求するときはいつでも、入力シーケンスにアイテムを要求します。関数がそれが良いと言えば、それを渡します。そうでない場合は、アイテムを要求し続けます。合格するものを手に入れるまで。」
したがって、それらを連鎖させると、ToList が最初の項目を要求すると、最初の項目として Where to に移動し、Where が Select に移動して最初の項目を要求し、Select がリストに移動して最初の項目を要求します。アイテム。次に、リストに最初の項目が表示されます。次に Select は、その項目を吐き出す必要のあるもの (この場合は int 0 のみ) に変換し、それを Where に渡します。Where はそのアイテムを受け取り、それが真であると判断する関数を実行し、リストに追加する ToList に 0 を吐き出します。そのすべてがさらに9回起こります。つまり、Select はリストの各項目を 1 回だけ要求し、その結果を Where に直接フィードします。Where は、「テストに合格した」結果を ToList に直接フィードし、ToList はそれらをリストに保存します。 .
これは最初は複雑に思えますが、実際にはコンピューターがこれらすべてを実行するのは非常に簡単です。実際には、最初に思われるほどパフォーマンスが集中するわけではありません。
Select
との組み合わせを使用することもできますがWhere
、これは独自の関数を作成するための良い候補となる可能性があります。
public static IEnumerable<int> Indexes<T>(IEnumerable<T> source, T itemToFind)
{
if (source == null)
throw new ArgumentNullException("source");
int i = 0;
foreach (T item in source)
{
if (object.Equals(itemToFind, item))
{
yield return i;
}
i++;
}
}
中間オブジェクトを必要としないため、受け入れられた回答と比較して、これは安価になります。
public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> filter, Func<TSource, int, TResult> selector)
{
int index = -1;
foreach (var s in source)
{
checked{ ++index; }
if (filter(s))
yield return selector(s, index);
}
}