如何使用LINQ获取索引?

cod*_*nix 306 .net c# linq c#-3.0

给定像这样的数据源:

var c = new Car[]
{
  new Car{ Color="Blue", Price=28000},
  new Car{ Color="Red", Price=54000},
  new Car{ Color="Pink", Price=9999},
  // ..
};
Run Code Online (Sandbox Code Playgroud)

如何用LINQ 找到满足一定条件的第一辆车的索引

编辑:

我可以想到这样的东西,但它看起来很糟糕:

int firstItem = someItems.Select((item, index) => new    
{    
    ItemName = item.Color,    
    Position = index    
}).Where(i => i.ItemName == "purple")    
  .First()    
  .Position;
Run Code Online (Sandbox Code Playgroud)

用一个普通的循环解决这个问题会是最好的吗?

Yur*_*ich 670

myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index;
Run Code Online (Sandbox Code Playgroud)

或稍短

myCars.Select((car, index) => new {car, index}).First(myCondition).index;
Run Code Online (Sandbox Code Playgroud)

  • 只是评论未来的搜索者C#6将允许`myCars.Select((car,index)=> new {car,index}).FirstOrDefault(myCondition)?. index;`在处理那里的情况时返回一个空索引应用myCondition后没有结果. (28认同)
  • 最大的区别在于,如果在这种情况下标记的答案返回-1的任何项目都不满足myCondition,则会抛出异常. (12认同)
  • 我刚用过它,对我来说效果很好.这与标记的答案有何不同? (5认同)
  • @ProfK小心使用`FirstOrDefault`,类的默认值为null,在null上调用.index并抛出异常. (5认同)
  • @ kape123:这当然也可以用于`Dictionary`和`Hashset`.显然,返回的索引不是作为有序集合中的索引"定义良好",但它仍然可以与`ElementAt`一起使用来检索匹配的元素. (2认同)
  • 索引是0基于. (2认同)
  • @YuriyFaktorovich我不明白它如何知道将'v'与汽车对象和'i'作为索引相关联.我看到"car = v,index = i"但运行时系统如何知道你的意思是你想要v中的car对象和i中数组中的索引? (2认同)

Red*_*wan 135

简单地说:

int index = List.FindIndex(your condition);
Run Code Online (Sandbox Code Playgroud)

例如

int index = cars.FindIndex(c => c.ID == 150);
Run Code Online (Sandbox Code Playgroud)

  • 对于数组,只需使用`Array.FindIndex`. (14认同)
  • @beluchin请记住,如果将`IEnumerable`转换为`List`,则"IEnumerable"不再是懒惰的.你强迫获得它的每一个**元素,即使你实际上并不需要它们. (10认同)
  • +1 - 虽然LINQ只处理IEnumerable,但这个答案让我意识到在我的情况下将IEnumerable转换为list然后调用`FindIndex`是可以的. (4认同)

SLa*_*aks 124

An IEnumerable不是有序集.
虽然大多数IEnumerables是有序的,但有些(例如DictionaryHashSet)不是.

因此,LINQ没有IndexOf方法.

但是,你可以自己写一个:

///<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)); }
Run Code Online (Sandbox Code Playgroud)

  • 虽然它确实有一个ElementAt方法.其中索引作为参数. (74认同)
  • @SLaks:使用依赖于订单的所有其他方法(ElementAt,First,Last,Skip和friends),我认为IndexOf不会太牵强. (12认同)
  • @ 280Z28:LINQ已经与`List <T>` - `FindAll(Predicate <T>)`与`Where(Func <T,bool>)`,`Exists(Predicate <T>)`vs.任何(Func <T,bool>)`,`ConvertAll(Converter <T,TOutput>)`与`Select(Func <T1,T2>)`等. (8认同)
  • 因为这就是所有其他LINQ方法所使用的.它使代理签名在工具提示中更清晰.`Predicate`,`Comparison`和朋友被.Net 3.5中的`Func`代表有效地取代. (6认同)
  • @SLaks,因为这个方法已经存在并且在`List <T>`类中是众所周知的,你应该将它命名为`FindIndex`并且它应该采用`Predicate <T>`而不是`Func <T,bool>` .`IndexOf`采用`T`对象而不是选择器.请记住,一致性是CLI的第一要点:http://msdn.microsoft.com/en-us/library/x1xzf2ca.aspx (5认同)
  • 第一点是令人困惑的,应该重新定义/删除.`IEnumerable`暴露了一个`IEnumerator`,它有两个成员:`MoveNext()`和`Current` - 它本身是有序的. (5认同)
  • @SLaks:这可能是真的,但1)在每种情况下,新名称不会在早期版本的类似上下文中存在名称,2)对于FindAll/Where和ConvertAll/Select,新版本具有不同的语义,并且在懒惰的评估环境中,旧名称没有意义,3)对于Exists/Any,旧名称具有误导性.你使用它作为你的答案的理由,但你没有遵循#1,#2肯定不适用,#3充其量是有争议的. (2认同)

Lum*_*mpN 79

myCars.TakeWhile(car => !myCondition(car)).Count();
Run Code Online (Sandbox Code Playgroud)

有用!想一想.第一个匹配项的索引等于它之前的(不匹配)项的数量.

讲故事的时间

我也不喜欢你在问题中提出的可怕的标准解决方案.就像接受的答案一样,我选择了一个普通的旧循环,尽管稍作修改:

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,它将返回项目数,而不是-1在没有匹配项时.但是现在让我们忽略这个小麻烦.事实上,可怕的标准解决方案在这种情况下崩溃,我考虑返回一个超出界限的索引.

现在发生的事情是ReSharper告诉我Loop可以转换成LINQ表达式.虽然大多数时候该功能会降低可读性,但这次结果令人敬畏.所以感谢JetBrains.

分析

优点

  • 简洁
  • 可与其他LINQ组合使用
  • 避免new匿名对象
  • 仅评估可枚举,直到谓词第一次匹配

因此我认为它在时间和空间上是最佳的,同时保持可读性.

缺点

  • 起初不太明显
  • -1没有匹配时不返回

当然,您始终可以将其隐藏在扩展方法后面.当没有匹配时,最好的做法取决于上下文.

  • `return index`而不是`break`内部循环将保留功能和可读性,并且如果没有找到元素,则很容易将最后的`return`转换为返回-1. (3认同)

Mar*_*zco 12

我会在这里做出贡献......为什么?只是因为:p它是一个基于Any LINQ扩展和委托的不同实现.这里是:

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();
}        
Run Code Online (Sandbox Code Playgroud)


jwi*_*ize 6

这是我刚刚放在一起的一个小扩展。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样称呼它。

IEnumerable<Int32> indicesWhereConditionIsMet = 
      ListItems.Positions(item => item == this);

Int32 firstWelcomeMessage ListItems.Position(msg =>               
      msg.WelcomeMessage.Contains("Hello"));
Run Code Online (Sandbox Code Playgroud)