Bei einer solchen Datenquelle:
var c = new Car[]
{
new Car{ Color="Blue", Price=28000},
new Car{ Color="Red", Price=54000},
new Car{ Color="Pink", Price=9999},
// ..
};
Wie finde ich das Index des ersten Autos, das mit LINQ eine bestimmte Bedingung erfüllt?
BEARBEITEN:
Ich könnte mir so etwas vorstellen, aber es sieht schrecklich aus:
int firstItem = someItems.Select((item, index) => new
{
ItemName = item.Color,
Position = index
}).Where(i => i.ItemName == "purple")
.First()
.Position;
Wird es das Beste sein, dies mit einer einfachen alten Schleife zu lösen?
Ein IEnumerable
ist keine geordnete Menge.
Obwohl die meisten IEnumerables geordnet sind, sind es einige (wie Dictionary
oder HashSet
) nicht.
Daher verfügt LINQ nicht über eine IndexOf
-Methode.
Sie können jedoch selbst eines schreiben:
///<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;
oder die etwas kürzer
myCars.Select((car, index) => new {car, index}).First(myCondition).index;
Mach einfach:
int index = List.FindIndex(your condition);
Z.B.
int index = cars.FindIndex(c => c.ID == 150);
myCars.TakeWhile(car => !myCondition(car)).Count();
Es klappt! Denk darüber nach. Der Index des ersten übereinstimmenden Elements entspricht der Nummer des (nicht übereinstimmenden) Elements davor.
Ich mag auch nicht die schreckliche Standardlösung , die Sie bereits in Ihrer Frage vorgeschlagen haben. Wie die akzeptierte Antwort entschied ich mich für eine einfache alte Schleife, obwohl mit einer geringfügigen Änderung:
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;
}
Beachten Sie, dass die Anzahl der Elemente anstelle von -1
Zurückgegeben wird, wenn keine Übereinstimmung vorliegt. Aber lassen Sie uns diesen kleinen Ärger vorerst ignorieren. Tatsächlich stürzt die schreckliche Standardlösung in diesem Fall ab und ich erwäge, einen Index zurückzugeben, der außerhalb der Grenzen überlegen ist .
Was jetzt passiert, sagt mir ReSharper Schleife kann in LINQ-Ausdruck umgewandelt werden . Während die meiste Zeit die Lesbarkeit verschlechtert, war das Ergebnis dieses Mal beeindruckend. Also ein großes Lob an die JetBrains.
new
von anonymen ObjektenDaher halte ich es für zeitlich und räumlich optimal und trotzdem lesbar.
-1
Zurück, wenn keine Übereinstimmung vorliegtNatürlich können Sie es immer hinter einer Erweiterungsmethode verstecken. Und was am besten zu tun ist, wenn es keine Übereinstimmung gibt, hängt stark vom Kontext ab.
Ich werde hier meinen Beitrag leisten ... warum? Nur weil: p Es handelt sich um eine andere Implementierung, die auf der Any LINQ-Erweiterung und einem Delegaten basiert. Hier ist es:
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();
}
Hier ist eine kleine Erweiterung, die ich gerade zusammengestellt habe.
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;
}
}
Dann können Sie es so nennen.
IEnumerable<Int32> indicesWhereConditionIsMet =
ListItems.Positions(item => item == this);
Int32 firstWelcomeMessage ListItems.Position(msg =>
msg.WelcomeMessage.Contains("Hello"));
Hier ist eine Implementierung der Antwort mit der höchsten Bewertung, die -1 zurückgibt, wenn das Element nicht gefunden wird:
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;
}