Question Comment obtenir un index en utilisant LINQ? [dupliquer]


Cette question a déjà une réponse ici:

É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 indice 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-t-il préférable de résoudre ce problème avec une vieille boucle?


257
2018-03-18 16:30


origine


Réponses:


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

Par conséquent, LINQ n'a pas de 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)); }

115
2018-03-18 16:35



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

ou le peu plus court

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

556
2018-03-18 16:33



Simplement faire:

int index = List.FindIndex(your condition);

Par exemple.

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

99
2017-11-27 10:12



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

Ça marche! Penses-y. L'index du premier élément correspondant est égal au nombre d'éléments (non correspondants) avant lui.

L'heure du conte

Moi aussi je n'aime pas la solution standard horrible vous avez déjà suggéré dans votre question. Comme la réponse acceptée, je suis allé pour une vieille boucle simple 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 quand il n'y a pas de correspondance. Mais ignorons cette gêne mineure pour le moment. En fait, le solution standard horrible se bloque dans ce cas et Je pense à retourner un index qui est hors limites supérieur.

Qu'est-ce qui se passe maintenant, ReSharper me dit La boucle peut être convertie en expression LINQ. Bien que la plupart du temps, cette fonctionnalité aggrave la lisibilité, cette fois le résultat était impressionnant. Alors félicitations pour les JetBrains.

Une analyse

Avantages

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

C'est pourquoi je le considère optimal dans le temps et dans l'espace tout en restant lisible.

Les inconvénients

  • Pas tout à fait évident au premier abord
  • 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 ce qu'il faut faire quand il n'y a pas de correspondance dépend fortement du contexte.


69
2017-10-18 16:05



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

11
2018-04-10 04:21



Voici une petite extension que je viens de mettre en place.

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
2018-02-12 20:43



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

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

2
2018-06-04 06:07