web-dev-qa-db-fra.com

Comment obtenir un index en utilisant LINQ?

Étant donné une source de données comme celle-ci:

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

Comment puis-je trouver le index de la première voiture satisfaisant une certaine condition avec LINQ?

MODIFIER:

Je pourrais penser à quelque chose comme ça mais ça a l'air horrible:

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

Sera-ce le mieux pour résoudre cela avec une vieille boucle simple?

295
codymanix

Un IEnumerable n'est pas un ensemble ordonné.
Bien que la plupart des IEnumerables soient commandés, certains (tels que Dictionary ou HashSet) ne le sont pas.

Par conséquent, LINQ n'a pas de méthode IndexOf.

Cependant, vous pouvez en écrire un vous-même:

///<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;

ou le légèrement plus court

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

Faites simplement:

int index = List.FindIndex(your condition);

Par exemple.

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

Ça marche! Pensez-y. L'index du premier élément correspondant est égal au nombre d'éléments (ne correspondant pas) qui le précèdent.

L'heure du conte

Moi aussi je n'aime pas la solution standard horrible que vous avez déjà suggérée dans votre question. Comme pour la réponse acceptée, je suis parti pour une boucle ancienne, bien qu’avec une légère modification:

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

Notez qu'il retournera le nombre d'éléments au lieu de -1 lorsqu'il n'y a pas de correspondance. Mais ignorons cette gêne mineure pour le moment. En fait, la solution standard horrible se bloque dans ce cas et j’envisage de renvoyer un indice qui est supérieur au-dessus des limites .

Qu'est-ce qui se passe maintenant, ReSharper me dit La boucle peut être convertie en expression LINQ . Alors que la plupart du temps, la fonctionnalité détériorait la lisibilité, cette fois, le résultat était impressionnant. Alors bravo aux JetBrains.

Une analyse

Avantages

  • Concis
  • Combinable avec d'autres LINQ
  • Evite newing objets anonymes
  • Évalue seulement l'énumérable jusqu'à ce que le prédicat corresponde pour la première fois

Par conséquent, je considère qu’il est optimal dans le temps et dans l’espace tout en restant lisible.

Les inconvénients

  • Pas tout à fait évident au début
  • Ne retourne pas -1 quand il n'y a pas de correspondance

Bien sûr, vous pouvez toujours le cacher derrière une méthode d'extension. Et quoi faire de mieux quand il n'y a pas de correspondance dépend fortement du contexte.

78
LumpN

Je vais faire ma contribution ici ... pourquoi? juste parce que: p C’est une implémentation différente, basée sur l’extension Any LINQ, et un délégué. C'est ici:

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

Voici une petite extension que je viens de mettre ensemble.

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

Ensuite, vous pouvez l'appeler comme ça.

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

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

Voici une implémentation de la réponse la plus votée qui renvoie -1 lorsque l'élément n'est pas trouvé:

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