Как получить индекс элемента в IEnumerable?
-
18-09-2019 - |
Вопрос
Я написал это:
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();
}
}
Но я не знаю, существует ли это уже, не так ли?
Решение
Весь смысл вывода данных в виде IEnumerable заключается в том, чтобы вы могли лениво перебирать содержимое.Как таковой, не существует действительно понятие индекса.То, что вы делаете, на самом деле не имеет большого смысла для IEnumerable.Если вам нужно что-то, поддерживающее доступ по индексу, поместите это в реальный список или коллекцию.
Другие советы
Я бы усомнился в этой мудрости, но, возможно:
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;
}
}
Способ, которым я сейчас это делаю, немного короче, чем уже предложенные, и, насколько я могу судить, дает желаемый результат:
var index = haystack.ToList().IndexOf(needle);
Это немного неуклюже, но оно выполняет свою работу и довольно лаконично.
Я думаю, что лучший вариант - это реализовать следующим образом:
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;
}
Это также не приведет к созданию анонимного объекта
Лучший способ поймать позицию - это 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);
Немного запоздал с игрой, я знаю...но это то, что я недавно сделал.Он немного отличается от вашего, но позволяет программисту диктовать, какой должна быть операция равенства (предикат).Который я нахожу очень полезным при работе с различными типами, поскольку тогда у меня есть общий способ сделать это независимо от типа объекта и <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, если не найден, не создает дополнительных объектов и следует короткое замыкание при обнаружении [в отличие от перебора всего 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);
}
Альтернативой поиску индекса постфактум является перенос перечислимого, что несколько похоже на использование метода 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);
}
Наткнулся на это сегодня в поисках ответов, и я подумал, что добавлю свою версию в список (без каламбура).Он использует нулевой условный оператор 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), в худшем случае нужный вам товар находится в конце, это 2x быстрее, чем делать ToList().FindIndex()
.Если нужный вам предмет находится посередине, его ~4x быстрее.
Для небольших коллекций (~ 10 000) это, кажется, лишь незначительно быстрее
Вот как я это тестировал https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e
Используя ответ @Marc Gravell , я нашел способ использовать следующий метод:
source.TakeWhile(x => x != value).Count();
для того, чтобы получить -1, когда элемент не может быть найден:
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
собственность.
Интерфейс:
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();