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

我写道:

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

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

257139 次浏览

我会质疑这是否明智,但也许:

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

我会这样实现它:

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

以 IEnumable 方式发布内容的全部意义在于,您可以对内容进行惰性迭代。因此,真的不是一个索引的概念。对于 IEnumable 来说,您所做的实际上没有多大意义。如果需要支持按索引访问的内容,请将其放在实际的列表或集合中。

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

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

它也不会创建匿名对象

在事后查找索引的另一种方法是包装 Enumable,这有点类似于使用 LinqGroupBy ()方法。

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

我现在做这件事的方式比我之前建议的方式要短一些,就我所知,我得到了我想要的结果:

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

虽然有点笨重,但它确实起到了作用,而且相当简洁。

我知道有点晚了,但这是我最近做的。它与您的略有不同,但是允许程序员规定相等操作需要是什么(谓词)。这在处理不同类型时非常有用,因为不管对象类型和内置的相等运算符 <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;
}

希望这能帮到别人。

几年后,这个函数使用 Linq,如果没有找到返回 -1,不会创建额外的对象,而且发现时 应该会短路(而不是遍历整个 IEnumable) :

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

获取位置的最佳方法是通过 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);

这可以变得非常酷的扩展(作为一个代理) ,例如:

collection.SelectWithIndex();
// vs.
collection.Select((item, index) => item);

它将自动为通过此 Index属性访问的集合分配索引。

界面:

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

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

public static class EnumerableXtensions
{
public static IEnumerable<TModel> SelectWithIndex<TModel>(
this IEnumerable<TModel> collection) where TModel : class, IIndexable
{
return collection.Select((item, index) =>
{
item.Index = index;
return item;
});
}
}


public class SomeModelDTO : IIndexable
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }


public int Index { get; set; }
}


// In a method
var items = from a in db.SomeTable
where a.Id == someValue
select new SomeModelDTO
{
Id = a.Id,
Name = a.Name,
Price = a.Price
};


return items.SelectWithIndex()
.OrderBy(m => m.Name)
.Skip(pageStart)
.Take(pageSize)
.ToList();

今天在寻找答案的过程中偶然发现了这个,我想我应该把我的版本加入到这个列表中(没有双关语的意思)。它使用了 c # 6.0的空条件运算符

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) ,最坏的情况下,你想要的项目是在最后,这是 2倍比做 ToList().FindIndex()快。如果您想要的项目是在中间它的 4倍更快。

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

以下是我如何测试它 翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳 https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e

试试这个:

static int FindIndex<T>(this IEnumerable<T> a, Predicate<T> f) =>
a.TakeWhile(x => !f(x)).Count();


static int IndexOf<T>(this IEnumerable<T> a, T value) =>
a.FindIndex(x => EqualityComparer<T>.Default.Equals(x, value));


var i = new[] { 1, 2, 3 }.IndexOf(2); // 1