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

问题描述 投票:125回答:12

我写了这个:

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();
    }
}

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

c# .net linq ienumerable indexof
12个回答
47
投票

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


1
投票

今天在寻找答案时偶然发现了这一点,我想我会将我的版本添加到列表中(没有双关语意)。它使用了c#6.0的null条件运算符

IEnumerable<Item> collection = GetTheCollection();

var index = collection
.Select((item,idx) => new { Item = item, Index = idx })
//or .FirstOrDefault(_ =>  _.Item.Prop == something)
.FirstOrDefault(_ => _.Item == itemToFind)?.Index ?? -1;

我做了一些'老马赛车'(测试)和大型收藏(~100,000),最糟糕的情况是你想要的项目在最后,这比做ToList().FindIndex()快2倍。如果你想要的物品位于中间,它的速度要快4倍。

对于较小的集合(约10,000),它似乎只是稍微快一点

这是我测试它的方式qazxsw poi


1
投票

使用@Marc Gravell的答案,我找到了一种使用以下方法的方法:

https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e

为了在无法找到项目时获得-1:

source.TakeWhile(x => x != value).Count();

我想这种方式既可以是最快也可以更简单。但是,我还没有测试过表演。


0
投票

这可以通过扩展(充当代理)变得非常酷,例如:

internal static class Utils
{

    public static int IndexOf<T>(this IEnumerable<T> enumerable, T item) => enumerable.IndexOf(item, EqualityComparer<T>.Default);

    public static int IndexOf<T>(this IEnumerable<T> enumerable, T item, EqualityComparer<T> comparer)
    {
        int index = enumerable.TakeWhile(x => comparer.Equals(x, item)).Count();
        return index == enumerable.Count() ? -1 : index;
    }
}

这将通过此collection.SelectWithIndex(); // vs. collection.Select((item, index) => item); 属性自动将索引分配给集合。

接口:

Index

自定义扩展(可能对使用EF和DbContext最有用):

public interface IIndexable
{
    int Index { get; set; }
}

109
投票

我会质疑智慧,但也许:

source.TakeWhile(x => x != value).Count();

(如果需要,使用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;
}

23
投票

我会像这样实现它:

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;
    }
}

14
投票

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

 var index = haystack.ToList().IndexOf(needle);

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


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;
}

它也不会创建匿名对象


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;
 }

希望这有助于某人。


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);
}

'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);
}

4
投票

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

int id = listMyObject.FindIndex(x => x.Id == 15); 

如果你有枚举器或数组使用这种方式

int id = myEnumerator.ToList().FindIndex(x => x.Id == 15); 

要么

 int id = myArray.ToList().FindIndex(x => x.Id == 15); 

1
投票

在事实之后找到索引的替代方法是包装Enumerable,有点类似于使用Linq GroupBy()方法。

public static class IndexedEnumerable
{
    public static IndexedEnumerable<T> ToIndexed<T>(this IEnumerable<T> items)
    {
        return IndexedEnumerable<T>.Create(items);
    }
}

public class IndexedEnumerable<T> : IEnumerable<IndexedEnumerable<T>.IndexedItem>
{
    private readonly IEnumerable<IndexedItem> _items;

    public IndexedEnumerable(IEnumerable<IndexedItem> items)
    {
        _items = items;
    }

    public class IndexedItem
    {
        public IndexedItem(int index, T value)
        {
            Index = index;
            Value = value;
        }

        public T Value { get; private set; }
        public int Index { get; private set; }
    }

    public static IndexedEnumerable<T> Create(IEnumerable<T> items)
    {
        return new IndexedEnumerable<T>(items.Select((item, index) => new IndexedItem(index, item)));
    }

    public IEnumerator<IndexedItem> GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

给出了一个用例:

var items = new[] {1, 2, 3};
var indexedItems = items.ToIndexed();
foreach (var item in indexedItems)
{
    Console.WriteLine("items[{0}] = {1}", item.Index, item.Value);
}
© www.soinside.com 2019 - 2024. All rights reserved.