É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?
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)); }
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;
Faites simplement:
int index = List.FindIndex(your condition);
Par exemple.
int index = cars.FindIndex(c => c.ID == 150);
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.
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.
new
ing objets anonymesPar conséquent, je considère qu’il est optimal dans le temps et dans l’espace tout en restant lisible.
-1
quand il n'y a pas de correspondanceBien 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.
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();
}
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"));
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;
}