387 votes

Comment obtenir un index en utilisant LINQ ?

Étant donné une source de données comme ça :

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 indice de la première voiture satisfaisant une certaine condition avec LINQ ?

EDIT :

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;

Le mieux serait-il de résoudre ce problème avec une simple boucle ?

0 votes

0 votes

Même cette information serait utile - stackoverflow.com/questions/4049773/

7 votes

En fait, il existe un index déclaration : var result = items.Select((item, index) => new { index, item });

830voto

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

ou la version légèrement plus courte

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

ou la version légèrement plus courte

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

5 votes

Je viens de l'utiliser et cela fonctionne bien pour moi. En quoi cela diffère-t-il de la réponse marquée ?

0 votes

Vous utilisez une collection ordonnée (comme Car[]), donc la réponse marquée ne s'applique pas à votre cas... vous ne pourriez pas utiliser ceci sur Dictionary ou Hashset.

2 votes

@kape123 : Cela peut certainement aussi être utilisé pour Dictionary y Hashset . Évidemment, l'index retourné n'est pas aussi "bien défini" qu'un index dans une collection ordonnée, mais il peut quand même être utilisé avec ElementAt pour récupérer l'élément correspondant.

188voto

Red Swan Points 3073

Il suffit de faire :

int index = List.FindIndex(your condition);

Par exemple

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

5 votes

+1 - bien que LINQ ne traite que les IEnumerable, cette réponse m'a fait réaliser que dans mon cas, il était correct de convertir l'IEnumerable en liste et ensuite d'appeler FindIndex

19 votes

Pour les tableaux, utilisez simplement Array.FindIndex .

12 votes

@beluchin Gardez à l'esprit que, si vous convertissez la IEnumerable à List el IEnumerable n'est plus paresseux. Vous êtes obligé d'obtenir Chaque éléments, même si vous n'en avez pas réellement besoin.

140voto

SLaks Points 391154

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

Par conséquent, LINQ ne dispose pas d'un IndexOf méthode.

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

83 votes

Bien qu'il dispose d'une méthode ElementAt. Qui prend un index comme argument.

1 votes

Oui ; je ne sais pas pourquoi cette méthode existe.

0 votes

C# a un délégué Prédicat pour vous, comment se fait-il que vous ayez utilisé Func<T, bool> ?

84voto

LumpN Points 2989
myCars.TakeWhile(car => !myCondition(car)).Count();

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

L'heure du conte

Moi aussi, je n'aime pas le horrible solution standard que vous avez déjà suggéré dans votre question. Comme la réponse acceptée, j'ai opté pour une bonne vieille boucle, mais 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 petite gêne pour l'instant. En fait, le horrible solution standard s'effondre dans ce cas et J'envisage de retourner un index qui est hors limites supérieur .

Ce qui se passe maintenant, c'est que ReSharper me dit La boucle peut être convertie en expression LINQ . Si, la plupart du temps, cette fonctionnalité nuit à la lisibilité, cette fois-ci, le résultat a été époustouflant. Félicitations à JetBrains.

Analyse

Pour

  • Concise
  • Combinable avec d'autres LINQ
  • Évite le site new ingérer des objets anonymes
  • N'évalue l'énumérable que lorsque le prédicat correspond pour la première fois.

Je considère donc qu'il est optimal dans le temps et l'espace tout en restant lisible.

Cons

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

Bien sûr, vous pouvez toujours la cacher derrière une méthode d'extension. Et ce qu'il convient de faire lorsqu'il n'y a pas de correspondance dépend fortement du contexte.

1 votes

L'ajout d'une simple variable booléenne isFound avant la boucle, la mise à true de cette variable avant l'instruction break et la comparaison avec cette variable avant le retour de la fonction résoudrait le problème de -1.

0 votes

En fait, j'avais besoin d'une requête LINQ qui renverrait le compte complet si aucun élément n'était trouvé, donc c'est parfait !

7 votes

return index au lieu de break La boucle intérieure conserverait la fonctionnalité et la lisibilité et faciliterait la conversion de la dernière version de l'article. return pour retourner -1 si aucun élément n'est trouvé.

13voto

Je vais apporter 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é. La voici :

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

0 votes

Au fait, le code est exécutable sur le LINQPad.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X