475 Stimmen

Wie lautet der Index mit LINQ abrufen?

Angenommen, Sie haben eine Datenquelle wie diese:

var c = new Car[]
{
  new Car{ Color="Blau", Price=28000},
  new Car{ Color="Rot", Price=54000},
  new Car{ Color="Pink", Price=9999},
  // ..
};

Wie kann ich den Index des ersten Autos finden, das eine bestimmte Bedingung mit LINQ erfüllt?

BEARBEITEN:

Ich habe an etwas wie das gedacht, aber es sieht schrecklich aus:

int firstItem = someItems.Select((item, index) => new    
{    
    ItemName = item.Color,    
    Position = index    
}).Where(i => i.ItemName == "Lila")    
  .First()    
  .Position;

Wäre es am besten, dies mit einer einfachen Schleife zu lösen?

0 Stimmen

0 Stimmen

Auch diese Informationen wären hilfreich - stackoverflow.com/questions/4049773/…

11 Stimmen

Eigentlich gibt es eine index Anweisung: var result = items.Select((item, index) => new { index, item });

954voto

Yuriy Faktorovich Punkte 64670
myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index;

oder die etwas kürzere

myCars.Select((car, index) => new {car, index}).First(myCondition).index;

oder die etwas kürzere kürzere

myCars.Select((car, index) => (car, index)).First(myCondition).index;

230voto

Red Swan Punkte 14483

Einfach machen:

int index = Liste.FindIndex(deine Bedingung);

Zum Beispiel:

int index = cars.FindIndex(c => c.ID == 150);

150voto

SLaks Punkte 832502

Ein IEnumerable ist keine geordnete Menge.
Obwohl die meisten IEnumerables geordnet sind, sind einige (wie z.B. Dictionary oder HashSet) dies nicht.

Deshalb hat LINQ keine IndexOf-Methode.

Sie können jedoch eine selbst schreiben:

///Sucht den Index des ersten Elements, das einer Bedingung in einem Enumerable entspricht.
///Der zu durchsuchende Enumerable.
///Der Ausdruck, gegen den die Elemente getestet werden sollen.
///Der Index des ersten passenden Elements oder -1, wenn keine Elemente übereinstimmen.
public static int FindIndex(this IEnumerable items, Func predicate) {
    if (items == null) throw new ArgumentNullException("items");
    if (predicate == null) throw new ArgumentNullException("predicate");

    int retVal = 0;
    foreach (var item in items) {
        if (predicate(item)) return retVal;
        retVal++;
    }
    return -1;
}
///Sucht den Index des ersten Vorkommens eines Elements in einem Enumerable.
///Der zu durchsuchende Enumerable.
///Das zu findende Element.
///Der Index des ersten passenden Elements oder -1, wenn das Element nicht gefunden wurde.
public static int IndexOf(this IEnumerable items, T item) { return items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); }

90voto

Jonas Bötel Punkte 4402
myCars.TakeWhile(car => !myCondition(car)).Count();

Es funktioniert! Denke darüber nach. Der Index des ersten übereinstimmenden Elements entspricht der Anzahl der (nicht übereinstimmenden) Elemente davor.

Geschichtsstunde

Auch ich mag die schreckliche Standardlösung nicht, die du bereits in deiner Frage vorgeschlagen hast. Wie die akzeptierte Antwort habe ich mich für eine ganz normale Schleife entschieden, allerdings mit einer kleinen Änderung:

public static int FindIndex(this IEnumerable items, Predicate predicate) {
    int index = 0;
    foreach (var item in items) {
        if (predicate(item)) break;
        index++;
    }
    return index;
}

Beachte, dass es die Anzahl der Elemente zurückgibt, anstatt -1, wenn es keine übereinstimmung gibt. Aber lassen wir uns von diesem kleinen Ärgernis vorerst nicht stören. Tatsächlich stürzt die schreckliche Standardlösung in diesem Fall ab und ich halte es für überlegen, einen Index zurückzugeben, der außerhalb der Grenzen liegt.

Jetzt sagt mir ReSharper, dass Schleifen in LINQ-Ausdrücke umgewandelt werden können. Während die Funktion die Lesbarkeit meistens verschlechtert, war das Ergebnis diesmal beeindruckend. Also Lob an JetBrains.

Analyse

Vorteile

  • Kurz
  • Kombinierbar mit anderen LINQ
  • Vermeidet das newen anonymer Objekte
  • Wertet nur die Auflistung aus, bis das Prädikat zum ersten Mal übereinstimmt

Daher halte ich es für optimal in Bezug auf Zeit und Platzbedarf und bleibt dabei lesbar.

Nachteile

  • Am Anfang nicht ganz offensichtlich
  • Gibt kein -1 zurück, wenn es keine Übereinstimmung gibt

Natürlich kannst du es immer hinter einer Erweiterungsmethode verbergen. Und was im Falle keiner übereinstimmung am besten zu tun ist, hängt stark vom Kontext ab.

14voto

Ich werde hier meinen Beitrag leisten... warum? einfach weil :p Es handelt sich um eine andere Implementierung, basierend auf der Any LINQ-Erweiterung und einem Delegaten. Hier ist es:

public static class Extensions
{
    public static int IndexOf(
            this IEnumerable list, 
            Predicate condition) {               
        int i = -1;
        return list.Any(x => { i++; return condition(x); }) ? i : -1;
    }
}

void Main()
{
    TestGetsFirstItem();
    TestGetsLastItem();
    TestGetsMinusOneOnNotFound();
    TestGetsMiddleItem();   
    TestGetsMinusOneOnEmptyList();
}

void TestGetsFirstItem()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d" };

    // Act
    int index = list.IndexOf(item => item.Equals("a"));

    // Assert
    if(index != 0)
    {
        throw new Exception("Index sollte 0 sein, ist aber: " + index);
    }

    "Test erfolgreich".Dump();
}

void TestGetsLastItem()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d" };

    // Act
    int index = list.IndexOf(item => item.Equals("d"));

    // Assert
    if(index != 3)
    {
        throw new Exception("Index sollte 3 sein, ist aber: " + index);
    }

    "Test erfolgreich".Dump();
}

void TestGetsMinusOneOnNotFound()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d" };

    // Act
    int index = list.IndexOf(item => item.Equals("e"));

    // Assert
    if(index != -1)
    {
        throw new Exception("Index sollte -1 sein, ist aber: " + index);
    }

    "Test erfolgreich".Dump();
}

void TestGetsMinusOneOnEmptyList()
{
    // Arrange
    var list = new string[] {  };

    // Act
    int index = list.IndexOf(item => item.Equals("e"));

    // Assert
    if(index != -1)
    {
        throw new Exception("Index sollte -1 sein, ist aber: " + index);
    }

    "Test erfolgreich".Dump();
}

void TestGetsMiddleItem()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d", "e" };

    // Act
    int index = list.IndexOf(item => item.Equals("c"));

    // Assert
    if(index != 2)
    {
        throw new Exception("Index sollte 2 sein, ist aber: " + index);
    }

    "Test erfolgreich".Dump();
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X