2008-10-26 2 views
15

Есть ли лучший способ сделать это?Получение коллекции значений индекса с использованием запроса LINQ

string[] s = {"zero", "one", "two", "three", "four", "five"}; 

var x = 
s 
.Select((a,i) => new {Value = a, Index = i}) 
.Where(b => b.Value.StartsWith("t")) 
.Select(c => c.Index); 

то я ищу для более эффективного и более изящным способом, чтобы получить позиции элементов, соответствующих критериям.

ответ

28

Вы можете легко добавить свой собственный метод расширения:

public static IEnumerable<int> IndexesWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate) 
{ 
    int index=0; 
    foreach (T element in source) 
    { 
     if (predicate(element)) 
     { 
      yield return index; 
     } 
     index++; 
    } 
} 

Затем используйте его:

string[] s = {"zero", "one", "two", "three", "four", "five"}; 
var x = s.IndexesWhere(t => t.StartsWith("t")); 
5

Кажется, мне очень хорошо. Вы можете сэкономить пару символов, изменяя выбор для:

.Select((Value, Index) => new {Value, Index}) 
+0

Спасибо - я не знал, что вы могли бы это сделать - я думал, вам нужно переназначить. – Guy 2008-10-26 02:07:23

+2

Он называется «инициализатором проекции» - он в основном принимает последнее подвыражение (которое должно быть полем или свойством) внутри выражения и использует его для имени. Таким образом, вы можете сделать x.GetFoo(). Bar, и это будет эквивалентно Bar = x.GetFoo(). – 2008-10-26 07:12:36

6

Если вы используете только пример, как способ узнать LINQ, игнорировать этот пост.


Непонятно, что LINQ на самом деле является лучшим способом сделать это. Код ниже кажется, что он будет более эффективным, так как не нужно создавать новый анонимный тип. Разумеется, ваш пример может быть изобретен, и этот метод может быть более полезным в другом контексте, например, в структуре данных, где он может использовать индекс по значению, но приведенный ниже код достаточно прост, понятен (без мысли требуется) и, возможно, более эффективно.

string[] s = {"zero", "one", "two", "three", "four", "five"}; 
List<int> matchingIndices = new List<int>(); 

for (int i = 0; i < s.Length; ++i) 
{ 
    if (s[i].StartWith("t")) 
    { 
     matchingIndices.Add(i); 
    } 
} 
+0

Спасибо за ответ. Я согласен, что это будет более эффективно. Как вы уже догадались, это упрощенная версия чего-то более сложного, что «есть» в LINQ в этом конкретном случае. – Guy 2008-10-26 05:23:34

2

Существует также FindIndex метод в списке Collection, для которого вы создаете метод удаления который может вернуть индекс из коллекции. вы можете обратиться к следующей ссылке в msdn http://msdn.microsoft.com/en-us/library/x1xzf2ca.aspx.

1

Как насчет этого? Это похоже на исходный плакат, но я сначала выбираю индексы, а затем создаю коллекцию, которая соответствует критериям.

var x = s.Select((a, i) => i).Where(i => s[i].StartsWith("t")); 

Это немного менее эффективно, чем некоторые другие ответы, поскольку список полностью повторяется дважды.

0

Я обсуждал эту интересную проблему с коллегой, и сначала я подумал, что решение JonSkeet было замечательным, но мой коллега указал на одну проблему, а именно: если функция является расширением до IEnumerable<T>, тогда ее можно использовать там, где коллекция Это.

С массивом, можно с уверенностью сказать, что заказ производится с foreach будет соблюден (т.е. foreach будет перебирать от первого до последнего), но это не обязательно в случае с другими коллекциями (список, словарь и т.д.), где foreach не отражает обязательно «порядок ввода». Однако функция есть, и это может ввести в заблуждение.

В конце концов, я в конечном итоге с чем-то подобным ответом tvanfosson, но как метод расширения, для массивов:

public static int[] GetIndexes<T>(this T[]source, Func<T, bool> predicate) 
{ 
    List<int> matchingIndexes = new List<int>(); 

    for (int i = 0; i < source.Length; ++i) 
    { 
     if (predicate(source[i])) 
     { 
      matchingIndexes.Add(i); 
     } 
    } 
    return matchingIndexes.ToArray(); 
} 

здесь надеюсь List.ToArray будет уважать заказ на последнюю операцию ...

Смежные вопросы