如何获取IEnumerable中元素的索引?

Jad*_*ias 134 .net c# linq ienumerable indexof

我写了这个:

public static class EnumerableExtensions
{
    public static int IndexOf<T>(this IEnumerable<T> obj, T value)
    {
        return obj
            .Select((a, i) => (a.Equals(value)) ? i : -1)
            .Max();
    }

    public static int IndexOf<T>(this IEnumerable<T> obj, T value
           , IEqualityComparer<T> comparer)
    {
        return obj
            .Select((a, i) => (comparer.Equals(a, value)) ? i : -1)
            .Max();
    }
}
Run Code Online (Sandbox Code Playgroud)

但我不知道它是否已经存在,是吗?

Mar*_*ell 115

我会质疑智慧,但也许:

source.TakeWhile(x => x != value).Count();
Run Code Online (Sandbox Code Playgroud)

(如果需要,可以使用EqualityComparer<T>.Default模拟!=) - 但如果没有找到,你需要注意返回-1 ...所以也许只是做很长的路

public static int IndexOf<T>(this IEnumerable<T> source, T value)
{
    int index = 0;
    var comparer = EqualityComparer<T>.Default; // or pass in as a parameter
    foreach (T item in source)
    {
        if (comparer.Equals(item, value)) return index;
        index++;
    }
    return -1;
}
Run Code Online (Sandbox Code Playgroud)

  • @Kamarey - 不,那确实有所不同.TakeWhile**在失败时停止**; Count(谓词)返回匹配的那些.即如果第一个是未命中而其他一切都是真的,TakeWhile(pred).Count()将报告0; Count(pred)将报告n-1. (8认同)
  • +1"质疑智慧".10次​​中有9次,这可能是一个坏主意. (7认同)
  • “TakeWhile”很聪明!请记住,如果元素不存在,则返回“Count”,这是与标准行为的偏差。 (3认同)

Sco*_*man 48

以IEnumerable为出发点的重点是你可以懒洋洋地迭代内容.因此,没有真正的索引概念.你正在做的事情对于IEnumerable来说真的没有多大意义.如果您需要支持索引访问的内容,请将其放在实际列表或集合中.

  • -1原因:有正当理由说明为什么要从"IEnumerable <>"中获取索引.我不买整个"你应该做这个"的教条. (196认同)
  • 同意@ ja72; 如果您不应该使用`IEnumerable`处理索引,那么`Enumerable.ElementAt`将不存在.`IndexOf`只是反过来 - 反对它的任何参数必须同样适用于`ElementAt`. (72认同)
  • 像Select((x,i)=> ...)这样的重载扩展似乎意味着这些索引应该存在 (13认同)
  • 目前我遇到了这个线程,因为我正在为IEnumerable <>类型实现一个通用的IList <>包装器,以便将我的IEnumerable <>对象与仅支持IList类型的数据源的第三方组件一起使用.我同意尝试在IEnumerable对象中获取元素的索引可能在大多数情况下是一些错误的标志,有时候发现这样的索引只是为了找到索引而在内存中再现一个大的集合当你已经拥有IEnumerable时,单个元素. (7认同)
  • Cleary,C#错过了IIndexableEnumerable的概念.在C++ STL术语中,这只是"随机可访问"概念的等价物. (6认同)
  • 为了使IndexOf有意义,必须对IEnumerable进行排序(不要与sorted混淆),并且必须能够多次枚举。我怀疑此方法不是LINQ的一部分,因为并非所有IEnumerable都满足这些条件。但是,在几乎所有要使用此功能的情况下,您都知道基础集合确实符合这些条件,因此我希望将其包括在内。 (3认同)

dah*_*byk 24

我会像这样实现它:

public static class EnumerableExtensions
{
    public static int IndexOf<T>(this IEnumerable<T> obj, T value)
    {
        return obj.IndexOf(value, null);
    }

    public static int IndexOf<T>(this IEnumerable<T> obj, T value, IEqualityComparer<T> comparer)
    {
        comparer = comparer ?? EqualityComparer<T>.Default;
        var found = obj
            .Select((a, i) => new { a, i })
            .FirstOrDefault(x => comparer.Equals(x.a, value));
        return found == null ? -1 : found.i;
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*tts 14

我现在这样做的方式比已经建议的方式要短一些,据我所知,给出了预期的结果:

 var index = haystack.ToList().IndexOf(needle);
Run Code Online (Sandbox Code Playgroud)

它有点笨重,但它完成了工作并且相当简洁.

  • 虽然这适用于小型集合,但假设您在"haystack"中有一百万个项目.在其上执行ToList()将遍历所有一百万个元素并将它们添加到列表中.然后它将搜索列表以查找匹配元素的索引.如果列表太大,这将是非常低效的以及抛出异常的可能性. (6认同)
  • @esteuart绝对 - 您需要选择适合您用例的方法.我怀疑是否有一个适合所有解决方案,这可能是核心库中没有实现的原因. (3认同)

小智 6

我认为最好的选择是这样实现:

public static int IndexOf<T>(this IEnumerable<T> enumerable, T element, IEqualityComparer<T> comparer = null)
{
    int i = 0;
    comparer = comparer ?? EqualityComparer<T>.Default;
    foreach (var currentElement in enumerable)
    {
        if (comparer.Equals(currentElement, element))
        {
            return i;
        }

        i++;
    }

    return -1;
}
Run Code Online (Sandbox Code Playgroud)

它也不会创建匿名对象


dan*_*004 6

抓住位置的最佳方法是通过FindIndex此功能仅适用于List<>

int id = listMyObject.FindIndex(x => x.Id == 15); 
Run Code Online (Sandbox Code Playgroud)

如果您有枚举器或数组,请使用这种方式

int id = myEnumerator.ToList().FindIndex(x => x.Id == 15); 
Run Code Online (Sandbox Code Playgroud)

要么

 int id = myArray.ToList().FindIndex(x => x.Id == 15); 
Run Code Online (Sandbox Code Playgroud)


Max*_*drv 5

游戏有点晚了,我知道......但这就是我最近所做的.它与您的略有不同,但允许程序员指定相等操作需要什么(谓词).我发现在处理不同类型时非常有用,因为我有一个通用的方法来做它,无论对象类型和<T>内置的相等运算符.

它还具有非常小的内存占用,并且非常,非常快速/高效...如果你关心它.

更糟糕的是,您只需将其添加到扩展列表中即可.

无论如何......在这里.

 public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
 {
     int retval = -1;
     var enumerator = source.GetEnumerator();

     while (enumerator.MoveNext())
     {
         retval += 1;
         if (predicate(enumerator.Current))
         {
             IDisposable disposable = enumerator as System.IDisposable;
             if (disposable != null) disposable.Dispose();
             return retval;
         }
     }
     IDisposable disposable = enumerator as System.IDisposable;
     if (disposable != null) disposable.Dispose();
     return -1;
 }
Run Code Online (Sandbox Code Playgroud)

希望这有助于某人.

  • 从IL来看,似乎性能上的差异在于,如果foreach实现了IDisposable,它将在枚举器上调用Dispose。(请参阅http://stackoverflow.com/questions/4982396/does-foreach-automatically-call-dispose)由于此答案中的代码不知道调用GetEnumerator的结果是否是一次性的,因此应该照着做。那时我还不清楚是否有性能方面的好处,尽管有一些额外的IL的目标并没有冲向我! (2认同)
  • 阅读这篇博客(http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx)可能是他的"迭代器循环"引用是一个`foreach`循环,在这种情况下,对于特殊情况,`T`是一个值类型,它可能通过使用while循环保存一个box/unbox操作.然而,我用你的回答用'foreach`得到的IL并没有证明这一点.不过,我仍然认为迭代器的有条件处理很重要.你可以修改答案来包含它吗? (2认同)

Gre*_*reg 5

几年后,但这使用Linq,如果找不到则返回-1,不创建额外的对象,并且应该在找到时短路[而不是遍历整个IEnumerable]:

public static int IndexOf<T>(this IEnumerable<T> list, T item)
{
    return list.Select((x, index) => EqualityComparer<T>.Default.Equals(item, x)
                                     ? index
                                     : -1)
               .FirstOr(x => x != -1, -1);
}
Run Code Online (Sandbox Code Playgroud)

'FirstOr'在哪里:

public static T FirstOr<T>(this IEnumerable<T> source, T alternate)
{
    return source.DefaultIfEmpty(alternate)
                 .First();
}

public static T FirstOr<T>(this IEnumerable<T> source, Func<T, bool> predicate, T alternate)
{
    return source.Where(predicate)
                 .FirstOr(alternate);
}
Run Code Online (Sandbox Code Playgroud)