É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 l’ 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-t-il préférable de résoudre ce problème avec une vieille boucle?
Un IEnumerable
n’est pas un ensemble ordonné.
Bien que la plupart des IEnumerables soient classé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:
///Finds the index of the first item matching an expression in an enumerable. ///The enumerable to search. ///The expression to test the items against. ///The index of the first matching item, or -1 if no items match. 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; } ///Finds the index of the first occurrence of an item in an enumerable. ///The enumerable to search. ///The item to find. ///The index of the first matching item, or -1 if the item was not found. public static int IndexOf (this IEnumerable items, T item) { return items.FindIndex(i => EqualityComparer .Default.Equals(item, i)); }
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;
Simplement faire:
int index = List.FindIndex(your condition);
Par exemple
int index = cars.FindIndex(c => c.ID == 150);
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.
Je n’aime pas non plus la solution standard horrible que vous avez déjà suggérée 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(this IEnumerable items, Predicate 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, l’ horrible solution standard se bloque dans ce cas et j’envisage de retourner un index qui est hors limites supérieur .
Ce qui arrive maintenant, c’est que ReSharper me dit que 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 aux JetBrains.
new
objects anonymes C’est pourquoi je le considère optimal dans le temps et dans l’espace tout en restant lisible.
-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.
Je vais faire ma consortingbution 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( 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 ssortingng[] { "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 ssortingng[] { "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 ssortingng[] { "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 ssortingng[] { }; // 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 ssortingng[] { "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(); }
Voici une petite extension que je viens de mettre en place.
public static class PositionsExtension { public static Int32 Position(this IEnumerable source, Func predicate) { return Positions (source, predicate).FirstOrDefault(); } public static IEnumerable Positions(this IEnumerable source, Func 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 indicesWhereConditionIsMet = ListItems.Positions(item => item == this); Int32 firstWelcomeMessage ListItems.Position(msg => msg.WelcomeMessage.Contains("Hello"));
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(this IEnumerable items, Func 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; }