Как получить индекс элемента в IEnumerable?

Я написал это:

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

Но я не знаю, существует ли он, не так ли?

Ответ 1

Весь смысл получать информацию как IEnumerable - это значит, что вы можете лениво перебирать содержимое. Таким образом, на самом деле нет понятия индекса. То, что вы делаете, действительно не имеет большого смысла для IEnumerable. Если вам требуется что-то, что поддерживает доступ по индексу, поместите его в фактический список или коллекцию.

Ответ 2

Я бы поставил под сомнение мудрость, но, возможно:

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

Ответ 3

Я бы выполнил его следующим образом:

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

Ответ 4

То, как я сейчас это делаю, немного короче, чем те, которые уже были предложены, и насколько я могу сказать, дает желаемый результат:

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

Это немного неуклюже, но он выполняет эту работу и довольно кратким.

Ответ 5

Я думаю, что лучший вариант - реализовать вот так:

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

Он также не будет создавать анонимный объект

Ответ 6

Немного поздно в игре, я знаю... но это то, что я недавно сделал. Он немного отличается от вашего, но позволяет программисту диктовать то, что должна выполняться операция равенства (предикат). Который я нахожу очень полезным при работе с разными типами, так как у меня есть общий способ сделать это независимо от типа объекта и <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;
 }

Надеюсь, это поможет кому-то.

Ответ 7

Несколько лет спустя, но это использует 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);
}

Ответ 8

Лучший способ уловить позицию - 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); 

Ответ 9

Альтернативой поиску индекса после факта является обертка 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);
}

Ответ 10

Наткнулся на это сегодня в поиске ответов, и я подумал, что добавлю свою версию в список (без каламбура). Он содержит нулевой условный оператор С# 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 раза быстрее.

Для небольших коллекций (~ 10000) это кажется лишь незначительно быстрее

Вот как я это проверял https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e

Ответ 11

Это может быть действительно круто с расширением (функционирующим как прокси), например:

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

Ответ 12

Используя ответ @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;
    }
}

Я думаю, что этот путь может быть как самым быстрым, так и простым. Однако я еще не тестировал выступления.