LINQ om array-indexen van een waarde te vinden

Ervan uitgaande dat ik de volgende stringarray heb:

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

Is het mogelijk om LINQ te gebruiken om een ​​lijst met indexen te krijgen die overeenkomen met één string?

Als voorbeeld zou ik willen zoeken naar de tekenreeks “avg” en een lijst krijgen met

2, 4

wat betekent dat “avg” te vinden is op str[2] en str[4].


Antwoord 1, autoriteit 100%

.Selectheeft een zelden gebruikte overbelasting die een index produceert. Je kunt het als volgt gebruiken:

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

Het resultaat is een lijst met 2 en 4.

Documentatie hier


Antwoord 2, autoriteit 18%

Je kunt het als volgt doen:

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

De belangrijkste stap is het gebruik van de overbelasting van Selectdie levert de huidige index aan uw functor.


Antwoord 3, autoriteit 6%

U kunt de overbelasting van Enumerable.Selectgebruiken die de index passeert en vervolgens Enumerable.Wheregebruiken op een anoniem type:

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

Als je alleen de eerste/laatste index wilt vinden, heb je ook de ingebouwde methoden List.IndexOfen List.LastIndexOf:

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

(of u kunt deze overbelastinggebruiken die een startindex heeft om de startpositie te specificeren)


Antwoord 4, autoriteit 3%

Ten eerste, uw code herhaalt de lijst niet twee keer, maar slechts één keer.

Dat gezegd hebbende, je Select krijgt eigenlijk gewoon een reeks van alle indexen; dat is gemakkelijker te doen met Enumerable.Range:

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

Het zal even wennen zijn om te begrijpen waarom de lijst niet twee keer wordt herhaald. Ik zal proberen een eenvoudige uitleg te geven.

Je moet de meeste LINQ-methoden, zoals Select en Where, beschouwen als een pijplijn. Elke methode doet een klein beetje werk. In het geval van Select geef je het een methode, en het zegt in feite: “Als iemand me om mijn volgende item vraagt, vraag ik eerst mijn invoerreeks voor een item, en gebruik dan de methode die ik heb om het in iets anders om te zetten, en geef dat item dan aan degene die me gebruikt.” Waar, min of meer, zegt: “wanneer iemand me om een ​​item vraagt, zal ik mijn invoervolgorde voor een item vragen, als de functie zegt dat het goed is, geef ik het door, zo niet, dan blijf ik om items vragen totdat ik er een krijg die slaagt.”

Dus als je ze koppelt, wat er gebeurt, is ToList om het eerste item te vragen, het gaat naar Waarheen als het voor het eerste item, Waar gaat naar Select en vraagt ​​het om het eerste item, Select gaat naar de lijst om het te vragen voor zijn eerste item. De lijst geeft dan het eerste item. Select transformeert dat item vervolgens in wat het moet uitspugen (in dit geval alleen de int 0) en geeft het aan Where. Waar neemt dat item en voert het zijn functie uit die bepaalt dat het waar is en spuugt dus 0 uit naar ToList, die het aan de lijst toevoegt. Dat hele gebeuren gebeurt dan nog 9 keer. Dit betekent dat Select uiteindelijk precies één keer naar elk item uit de lijst zal vragen, en het zal elk van de resultaten rechtstreeks naar Where sturen, die de resultaten die “voor de test slagen” rechtstreeks naar ToList zullen sturen, die ze opslaat in een lijst . Alle LINQ-methoden zijn zorgvuldig ontworpen om de bronsequentie slechts één keer te herhalen (wanneer ze één keer worden herhaald).

Houd er rekening mee dat, hoewel dit op het eerste gezicht ingewikkeld lijkt, het voor de computer eigenlijk vrij eenvoudig is om dit allemaal te doen. Het is niet zo prestatie-intensief als het op het eerste gezicht lijkt.


Antwoord 5, autoriteit 2%

Hoewel je een combinatie van Selecten Wherezou kunnen gebruiken, is dit waarschijnlijk een goede kandidaat om je eigen functie te maken:

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

Antwoord 6

Je hebt een gecombineerde select and where-operator nodig, in vergelijking met het geaccepteerde antwoord is dit goedkoper, omdat er geen tussenliggende objecten nodig zijn:

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

Other episodes