it-swarm.com.de

Wie erhalte ich einen Index mit LINQ?

Bei einer solchen Datenquelle:

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

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

BEARBEITEN:

Ich könnte mir so etwas vorstellen, aber es sieht schrecklich aus:

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

Wird es das Beste sein, dies mit einer einfachen alten Schleife zu lösen?

295
codymanix

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

Daher verfügt LINQ nicht über eine IndexOf -Methode.

Sie können jedoch selbst eines schreiben:

///<summary>Finds the index of the first item matching an expression in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="predicate">The expression to test the items against.</param>
///<returns>The index of the first matching item, or -1 if no items match.</returns>
public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> 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;
}
///<summary>Finds the index of the first occurrence of an item in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="item">The item to find.</param>
///<returns>The index of the first matching item, or -1 if the item was not found.</returns>
public static int IndexOf<T>(this IEnumerable<T> items, T item) { return items.FindIndex(i => EqualityComparer<T>.Default.Equals(item, i)); }
121
SLaks
myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index;

oder die etwas kürzer

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

Mach einfach:

int index = List.FindIndex(your condition);

Z.B.

int index = cars.FindIndex(c => c.ID == 150);
129
Red Swan
myCars.TakeWhile(car => !myCondition(car)).Count();

Es klappt! Denk darüber nach. Der Index des ersten übereinstimmenden Elements entspricht der Nummer des (nicht übereinstimmenden) Elements davor.

Märchenstunde

Ich mag auch nicht die schreckliche Standardlösung , die Sie bereits in Ihrer Frage vorgeschlagen haben. Wie die akzeptierte Antwort entschied ich mich für eine einfache alte Schleife, obwohl mit einer geringfügigen Änderung:

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

Beachten Sie, dass die Anzahl der Elemente anstelle von -1 Zurückgegeben wird, wenn keine Übereinstimmung vorliegt. Aber lassen Sie uns diesen kleinen Ärger vorerst ignorieren. Tatsächlich stürzt die schreckliche Standardlösung in diesem Fall ab und ich erwäge, einen Index zurückzugeben, der außerhalb der Grenzen überlegen ist .

Was jetzt passiert, sagt mir ReSharper Schleife kann in LINQ-Ausdruck umgewandelt werden . Während die meiste Zeit die Lesbarkeit verschlechtert, war das Ergebnis dieses Mal beeindruckend. Also ein großes Lob an die JetBrains.

Analyse

Vorteile

  • Prägnant
  • Kombinierbar mit anderen LINQ
  • Vermeidet das newvon anonymen Objekten
  • Wertet nur die Aufzählung aus, bis das Prädikat zum ersten Mal übereinstimmt

Daher halte ich es für zeitlich und räumlich optimal und trotzdem lesbar.

Nachteile

  • Zunächst nicht ganz klar
  • Gibt nicht -1 Zurück, wenn keine Übereinstimmung vorliegt

Natürlich können Sie es immer hinter einer Erweiterungsmethode verstecken. Und was am besten zu tun ist, wenn es keine Übereinstimmung gibt, hängt stark vom Kontext ab.

78
LumpN

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

public static class Extensions
{
    public static int IndexOf<T>(
            this IEnumerable<T> list, 
            Predicate<T> 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 should be 0 but is: " + index);
    }

    "Test Successful".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 should be 3 but is: " + index);
    }

    "Test Successful".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 should be -1 but is: " + index);
    }

    "Test Successful".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 should be -1 but is: " + index);
    }

    "Test Successful".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 should be 2 but is: " + index);
    }

    "Test Successful".Dump();
}        
12

Hier ist eine kleine Erweiterung, die ich gerade zusammengestellt habe.

public static class PositionsExtension
{
    public static Int32 Position<TSource>(this IEnumerable<TSource> source,
                                          Func<TSource, bool> predicate)
    {
        return Positions<TSource>(source, predicate).FirstOrDefault();
    }
    public static IEnumerable<Int32> Positions<TSource>(this IEnumerable<TSource> source, 
                                                        Func<TSource, bool> predicate)
    {
        if (typeof(TSource) is IDictionary)
        {
            throw new Exception("Dictionaries aren't supported");
        }

        if (source == null)
        {
            throw new ArgumentOutOfRangeException("source is null");
        }
        if (predicate == null)
        {
            throw new ArgumentOutOfRangeException("predicate is null");
        }
        var found = source.Where(predicate).First();
        var query = source.Select((item, index) => new
            {
                Found = ReferenceEquals(item, found),
                Index = index

            }).Where( it => it.Found).Select( it => it.Index);
        return query;
    }
}

Dann können Sie es so nennen.

IEnumerable<Int32> indicesWhereConditionIsMet = 
      ListItems.Positions(item => item == this);

Int32 firstWelcomeMessage ListItems.Position(msg =>               
      msg.WelcomeMessage.Contains("Hello"));
4
jwize

Hier ist eine Implementierung der Antwort mit der höchsten Bewertung, die -1 zurückgibt, wenn das Element nicht gefunden wird:

public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate)
{
    var itemsWithIndices = items.Select((item, index) => new { Item = item, Index = index });
    var matchingIndices =
        from itemWithIndex in itemsWithIndices
        where predicate(itemWithIndex.Item)
        select (int?)itemWithIndex.Index;

    return matchingIndices.FirstOrDefault() ?? -1;
}
3
Sam