Подтвердить что ты не робот

LINQ для поиска индексов массива значения

Предполагая, что у меня есть следующий массив строк:

string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"}

Возможно ли использовать LINQ для получения списка индексов, соответствующих одной строке?

В качестве примера я хотел бы найти строку "avg" и получить список, содержащий

2, 4

означает, что "avg" можно найти на str [2] и str [4].

4b9b3361

Ответ 1

.Select используется редко используемая перегрузка, которая создает индекс. Вы можете использовать его следующим образом:

str.Select((s, i) => new {i, s})
    .Where(t => t.s == "avg")
    .Select(t => t.i)
    .ToList()

В результате будет список, содержащий 2 и 4.

Документация здесь

Ответ 2

Вы можете сделать это следующим образом:

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, которая передает текущий указатель вашему функтору.

Ответ 3

Вы можете использовать перегрузку Enumerable.Select, которая передает индекс, а затем использовать Enumerable.Where для анонимного типа:

List<int> result = str.Select((s, index) => new { s, index })
                      .Where(x => x.s== "avg")
                      .Select(x => x.index)
                      .ToList();

Если вы просто хотите найти первый/последний индекс, у вас также есть встроенные методы List.IndexOf и List.LastIndexOf:

int firstIndex = str.IndexOf("avg");
int lastIndex = str.LastIndexOf("avg");

(или вы можете использовать эту перегрузку, которые принимают начальный индекс, чтобы указать начальную позицию)

Ответ 4

Пока вы можете использовать комбинацию 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++;
    }
}

Ответ 5

Во-первых, ваш код на самом деле не перебирает по списку дважды, он только выполняет его повторный запуск.

Тем не менее, ваш Select действительно просто получает последовательность всех индексов; это проще сделать с помощью Enumerable.Range:

 var result = Enumerable.Range(0, str.Count)
                 .Where(i => str[i] == "avg")
                 .ToList();

Понимание того, почему список на самом деле не повторяется дважды, немного привыкнет. Я попытаюсь дать основное объяснение.

Вы должны подумать о большинстве методов LINQ, таких как Select и Where as pipe. Каждый метод выполняет небольшую работу. В случае Select вы даете ему метод, и он, по сути, говорит: "Когда кто-то спрашивает меня о моем следующем элементе, я сначала спрошу свою последовательность ввода для элемента, а затем воспользуюсь методом, который я должен преобразовать в другое, а затем дайте этот предмет тому, кто меня использует". Где, более или менее, говорит: "Всякий раз, когда кто-то спрашивает меня об элементе, я попрошу ввести последовательность ввода для элемента, если функция скажет, что это хорошо, я передам его, если нет, я буду продолжать запрашивать предметы пока я не получу тот, который пройдет".

Итак, когда вы связываете их с тем, что происходит, ToList запрашивает первый элемент, он переходит к пункту Where to as it for the first item, Where to Select и запрашивает его для первого элемента. Выберите, чтобы перейти к списку, чтобы задать его для его первого элемента. Затем список предоставляет первый элемент. Затем этот выбор преобразует этот элемент в то, что ему нужно выплевывать (в данном случае просто int 0), и передает его в "Где". Где берет этот элемент и запускает его функцию, которая определяет, что она истинна и поэтому выплескивает 0 в ToList, что добавляет ее в список. Тогда все это происходит еще 9 раз. Это означает, что Select в конечном итоге будет запрашивать каждый элемент из списка ровно один раз, и он будет передавать каждый его результат непосредственно в "Где", который будет кормить результаты, которые "передают тест" непосредственно в ToList, который хранит их в списке, Все методы LINQ тщательно разработаны, чтобы только когда-либо перебирать исходную последовательность (когда они повторяются один раз).

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

Ответ 6

Вам нужен комбинированный оператор select и where, по сравнению с принятым ответом, это будет дешевле, так как не потребует промежуточных объектов:

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);
            }
        }