Получить индекс n-го элемента с определенным условием

Допустим, у нас есть List<int> с таким содержимым, как [0,0,0,0,1,1,1,1,0,0,0,1,2,2,0,0,2,2], и мы хотим иметь индекс n-го числа, который не равен нулю.

Например, GetNthNotZero(3) должен вернуть 6.

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

Да, но извлечение индекса немного усложняет ситуацию, потому что большинство операций LINQ работают с последовательностью, а не с индексами. Заметным исключением является Select, который позволяет нам делать что-то вроде int GetNthNotZero(IEnumerable<int> s, int ordinal) => s.Select((value, index) => (value, index)).Where(t => t.value != 0).ElementAt(ordinal - 1).index;. Обратите внимание, что это гораздо менее эффективно, чем простой цикл, с построением промежуточных последовательностей, и, возможно, не намного проще для понимания.

Jeroen Mostert 18.03.2022 11:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
85
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Стандартного метода не существует, но задумывались ли вы о написании собственного метода расширения, чтобы обеспечить что-то похожее на LINQ FindIndex()?

class Program
{
    static void Main(string[] args)
    {
        var list = new List<int>{ 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 2, 2, 0, 0, 2, 2 };

        var index = list.FindNthIndex(x => x > 0, 3);
    }
}

public static class IEnumerableExtensions
{
    public static int FindNthIndex<T>(this IEnumerable<T> enumerable, Predicate<T> match, int count)
    {
        var index = 0;

        foreach (var item in enumerable)
        {
            if (match.Invoke(item))
                count--;
            if (count == 0)
                return index;
            index++;
        }

        return -1;
    }
}

Я выбираю это в качестве ответа, хотя, строго говоря, он не обеспечивает стиль «использовать только что существующий LINQ», который предлагает вопрос, но я думаю, что это наиболее прямое решение и поддерживает чистоту бизнес-логики при расширении LINQ функции, которые можно использовать в кодовой базе

sctty 18.03.2022 19:06

На самом деле вы можете сделать это со стандартным LINQ, вы можете использовать:

List<int> sequence = new List<int>{0,0,0,0,1,1,1,1,0,0,0,1,2,2,0,0,2,2};
int index = sequence.Select((x, ix) => (Item:x, Index:ix))
    .Where(x => x.Item != 0)
    .Skip(2)  // you want the 3rd, so skip 2
    .Select(x => x.Index)
    .DefaultIfEmpty(-1) // if there is no third matching condition you get -1
    .First(); // result: 6

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

Cj S. 18.03.2022 13:51

@CjS.: Почему бы и нет? Я не пропускаю нули, я пропускаю условия совпадения, поэтому != 0. OP хочет индекс третьего условия совпадения, поэтому я пропускаю первые два совпадения

Tim Schmelter 18.03.2022 13:52

О - я вижу - Где нули опустились - Это умно!

Cj S. 18.03.2022 13:58

@CjS.: Вот сетевая рабочий пример, поиграй с ней: dotnetfiddle.net/0ZEg5e

Tim Schmelter 18.03.2022 13:59

Работает как шарм и, строго говоря, является ответом на вопрос. Хотя я считаю, что этот LINQ по-прежнему хорошо читается и понятен, я больше склоняюсь к использованию метода расширения, когда LINQ превышает некоторую сложность для довольно простой задачи. Я придумал более простое решение. Интересно посмотреть, как этого можно добиться с помощью базовых функций LINQ.

sctty 18.03.2022 19:09

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

Две существенные сложности, связанные с использованием Linq:

  1. Обработка пустой последовательности или последовательности без нулей.
  2. Синтез индекса для использования.

Решение Linq может выглядеть следующим образом (но обратите внимание, что, вероятно, существует множество различных возможных подходов с использованием Linq):

using System;
using System.Collections.Generic;
using System.Linq;

public static class Program
{
    public static void Main()
    {
        var ints = new List<int> { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 2, 2, 0, 0, 2, 2 };

        Console.WriteLine(IndexOfNthNotZero(ints, 3));                     // 6
        Console.WriteLine(IndexOfNthNotZero(Enumerable.Repeat(0, 10), 3)); // -1
        Console.WriteLine(IndexOfNthNotZero(ints, 100));                   // -1
        Console.WriteLine(IndexOfNthNotZero(Array.Empty<int>(), 0));       // -1
    }

    public static int IndexOfNthNotZero(IEnumerable<int> sequence, int n)
    {
        return sequence
            .Select((v, i) => (value:v, index:i))       // Synthesize the value and index.
            .Where(item => item.value != 0)             // Choose only the non-zero value.
            .Skip(n-1)                                  // Skip to the nth value. 
            .FirstOrDefault((value:0, index:-1)).index; // Handle missing data by supplying a default index of -1.
    }
}

Обратите внимание, что эта реализация возвращает -1, чтобы указать, что подходящее значение не найдено.

Сравните это с реализацией простого цикла, и я думаю, вы согласитесь, что лучше использовать простой цикл!

public static int IndexOfNthNotZero(IReadOnlyList<int> sequence, int n)
{
    for (int i = 0; i < sequence.Count; ++i)
        if (sequence[i] != 0 && --n == 0) // If element matches, decrement n and return index if it reaches 0.
            return i;

    return -1;
}

Или, если вы предпочитаете (избегая предварительного декремента):

public static int IndexOfNthNotZero(IReadOnlyList<int> sequence, int n)
{
    for (int i = 0, numberOfMatches = 0; i < sequence.Count; ++i)
    {
        if (sequence[i] != 0)           // If condition matches
            if (++numberOfMatches == n) // Increment number of matches, and if it reaches n
                return i;               // then return the current index
    }

    return -1;
}

Я не согласен с простым циклом из-за удобочитаемости. Игнорируя имя метода (которое можно сделать описательным в любом случае), для разбора цикла требуется больше усилий, чем для чтения цепочки LINQ построчно. Если бы вы показали мне только фрагменты без дальнейшего контекста; Я мог бы рассказать вам, что это делает быстрее с помощью фрагмента LINQ. Не то чтобы какой-либо из фрагментов непостижимо сложен; но я чувствую, что читабельность LINQ - это большой плюс, который ответ несколько не оценивает.

Flater 18.03.2022 12:00

@Flater Это явно вопрос мнения. Мне потребовались секунды, чтобы написать этот простой цикл, и гораздо больше времени, чтобы написать Linq из-за необходимости возиться с граничными условиями. Можете ли вы взглянуть на Linq и сразу сказать, что происходит, если последовательность пуста? Или если он не содержит совпадающих элементов? Или если количество совпадающих элементов меньше n? Вы можете сказать все эти вещи с первого взгляда в простом цикле, IMO.

Matthew Watson 18.03.2022 12:18

Я предполагаю, что это сводится к опыту LINQ, поскольку вопросы, которые вы задаете, (на мой взгляд) тривиально видны. LINQ использует пустые коллекции настолько часто, насколько это возможно; единственные исключения — это когда вы разворачиваете коллекцию в один элемент (First и Single), поэтому существует вариант OrDefault для изящной обработки. Для всех трех вопросов, которые вы задаете, один из связанных методов приводит к пустой коллекции (соответственно Select, Where, Skip). В каждом случае FirstOrDefault получает пустую коллекцию, возвращая значение по умолчанию, поскольку первого элемента нет.

Flater 18.03.2022 13:41

Дело также не столько в том, что мне нравится синтаксис LINQ, сколько в том, что я нахожу && --n == 0 действительно нечитаемое скрытое условие счетчика. Вам нужно знать об оценке короткого замыкания и различать --n и n--, а при внесении изменений вам действительно нужно посчитать, когда ваше состояние правильно достигает фазы выхода. Из-за этого логика счетчика очень чувствительна к ошибке «отключение на единицу». Не то чтобы это не работало, но его сложнее разобрать и легче пропустить, что вы где-то допустили ошибку.

Flater 18.03.2022 13:45

@flater Как я уже сказал, это вопрос мнения. Я определенно думаю, что какая-то простая реализация цикла лучше, чем решение Linq для этой конкретной проблемы.

Matthew Watson 18.03.2022 17:06

Это действительно дело вкуса, а также консистенции. Для кодовой базы, над которой я работаю, все эти задачи реализуются с помощью некоторого LINQ, поэтому здесь я склоняюсь к согласованности. Если вы найдете LINQ или цикл более читаемым, это действительно зависит от вашего опыта. Один вопрос: ".FirstOrDefault((значение:0, индекс:-1)).index;" для меня сбой компилятора docs.microsoft.com/en-us/dotnet/csharp/misc/… (VS2019, целевая платформа .NET5), это только у меня?

sctty 18.03.2022 19:13

@sctty FirstOrDefault() со значением по умолчанию только .NET 6.0 или более поздней версии, и вы используете только .NET 5.0, так что это объясняет.

Matthew Watson 19.03.2022 10:39

Другие вопросы по теме