Есть лучший способ сделать это?
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);
то есть я ищу более эффективный или более элегантный способ получить позиции элементов, соответствующие критериям.





Мне кажется, это нормально. Вы можете сохранить пару символов, изменив выбор на:
.Select((Value, Index) => new {Value, Index})
Он называется «инициализатором проекции» - он в основном берет последнее подвыражение (которое должно быть полем или свойством) внутри выражения и использует его для имени. Таким образом, вы можете выполнить x.GetFoo (). Bar, и это будет эквивалентно Bar = x.GetFoo (). Bar.
Если вы просто используете этот пример как способ изучить 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);
}
}
Спасибо за ответ. Я согласен, что так было бы эффективнее. Как вы уже догадались, это упрощенная версия чего-то более сложного, что «должно» быть сделано в LINQ в данном конкретном случае.
И этот точный код работает только тогда, когда вы используете List вместо произвольного IEnumerable. В противном случае вам пришлось бы использовать foreach и отдельную локальную переменную.
Вы можете легко добавить свой собственный метод расширения:
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"));
В списке коллекций также есть метод FindIndex, для которого вы создаете метод удаления, который может возвращать индекс из коллекции. вы можете обратиться к следующей ссылке в msdn http://msdn.microsoft.com/en-us/library/x1xzf2ca.aspx.
Как насчет этого? Он похож на исходный плакат, но сначала я выбираю индексы, а затем создаю коллекцию, соответствующую критериям.
var x = s.Select((a, i) => i).Where(i => s[i].StartsWith("t"));
Это немного менее эффективно, чем некоторые другие ответы, поскольку список полностью повторяется дважды.
Я обсуждал эту интересную проблему с коллегой, и сначала я подумал, что решение 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 соблюдает порядок последней операции ...
«Но функция есть, и она может вводить в заблуждение». - но это будет то же самое для любого из других методов LINQ, которые используют индексы, например .Select((item, index) => . Я не думаю, что с функцией Джона что-то не так, если вы понимаете, что получаете.
@Rup С тех пор я получил больше опыта работы с LINQ и интерфейсом IEnumerable. Я согласен с тем, что если вы знаете, что делаете, и если функция определена, сюрпризов не будет. Одна из потенциальных опасностей - получить индексы из IEnumerable, созданного с помощью AsParallel. Эти индексы будут отражать состояние создания коллекции, но создание ее снова почти гарантированно даст другой результат. Индексы будут относиться к тому, чего не существует. В командной работе (или при повторном использовании функции позже), если это поведение непонятно, у вас будут проблемы.
Я все еще думаю, что порядок вряд ли изменится, если вы не измените коллекцию, и что вам не следует использовать индексы, если это не имеет смысла для структуры. Но я не рассматривал AsParallel - здорово, спасибо.
Спасибо - я не знал, что ты можешь это сделать - я думал, тебе нужно переназначить.