IEnumerable에서 요소의 인덱스를 얻는 방법은 무엇입니까?


144

나는 이것을 썼다 :

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

그러나 이미 존재하는지 모르겠습니다.


4
Max접근 방식 의 문제점은 다음과 같습니다. a : 계속 찾고 있으며 b : 중복이있을 때 마지막 색인을 반환합니다 (사람들은 일반적으로 첫 번째 색인을 기대 함)
Marc Gravell

1
geekswithblogs.net 은 4 가지 솔루션과 성능을 비교합니다. ToList()/FindIndex()트릭을 수행하는 가장
nixda

답변:


51

IEnumerable로 물건을 가져 오는 요점은 내용을 느리게 반복 할 수 있다는 것입니다. 따라서 실제로 인덱스 개념 이 없습니다 . 당신이하고있는 일은 실제로 IEnumerable에 많은 의미가 없습니다. 인덱스 별 액세스를 지원하는 것이 필요하면 실제 목록이나 컬렉션에 넣으십시오.


8
현재 IEnumerable <> 유형에 대해 일반적인 IList <> 래퍼를 구현하여 IList 유형의 데이터 소스 만 지원하는 타사 구성 요소와 함께 IEnumerable <> 객체를 사용하기 때문에이 스레드에 도달했습니다. 나는 IEnumerable 객체 내에서 요소의 색인을 얻는 것이 아마도 대부분의 경우 잘못된 것으로 보이는 일의 징후는 색인을 찾기 위해 메모리에서 큰 컬렉션을 재현하는 경우가 종종 있습니다. 이미 IEnumerable이있을 때 단일 요소의
jpierson

215
-1 원인 :에서 인덱스를 얻으려는 정당한 이유가 있습니다 IEnumerable<>. 나는 "당신이해야 할 일"교리 전체를 구입하지는 않습니다.
John Alexiou

78
@ ja72에 동의하십시오; 당신이 인덱스를 처리하지 않아야하는 경우 IEnumerable다음 Enumerable.ElementAt존재하지 않을 것입니다. IndexOf단순히 반대 ElementAt입니다. 이에 대한 모든 주장은 동등하게 적용되어야합니다 .
Kirk Woll

7
분명히 C #은 IIndexableEnumerable이라는 개념을 빠뜨 렸습니다. 이는 C ++ STL 용어에서 "임의의 접근 가능한"개념과 동일합니다.
v.oddou

14
Select ((x, i) => ...)와 같은 과부하가있는 확장은 이러한 인덱스가 존재해야 함을 나타냅니다.
Michael

126

나는 지혜에 의문을 제기하지만 아마도 :

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

8
"지능 탐구"에 +1 10 중 9 번은 아마도 처음에는 나쁜 생각 일 것입니다.
Joel Coehoorn

명시 적 루프 솔루션은 Select (). Max () 솔루션보다 2 배 더 빠릅니다 (최악의 경우).
Steve Guidi

1
TakeWhile없이 람다로 요소 수를 계산할 수 있습니다. 루프를 하나만 저장합니다. source.Count (x => x! = value);
Kamarey

10
@Kamarey-아니, 다른 일을한다. TakeWhile은 중지 는 실패를 얻을 때; Count (predicate)는 일치하는 것을 반환합니다. 즉, 첫 번째 실수이고 다른 모든 것이 사실이면 TakeWhile (pred) .Count ()는 0을보고합니다. Count (pred)는 n-1을보고합니다.
Marc Gravell

1
TakeWhile영리하다! Count요소가 존재하지 않으면 표준 동작에서 벗어난 경우이 값 이 반환 된다는 점 을 명심하십시오 .
nawfal

27

나는 이것을 다음과 같이 구현할 것이다 :

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

1
실제로 매우 귀엽다, +1! 추가 객체가 필요하지만 비교적 저렴 해야 (GEN0) 큰 문제는 아닙니다. 는 ==작업을해야 할 수도 있습니다?
Marc Gravell

1
진정한 LINQ 스타일의 IEqualityComparer 오버로드가 추가되었습니다. ;)
dahlbyk

1
나는 당신이 ... comparer.Equals (xa, value) =)라고 말하려는 것 같아요
Marc

Select식이 결합 된 결과를 반환하고 처리되므로 KeyValuePair 값 형식을 명시 적으로 사용하면 .NET impl이 스택에 값 형식을 할당하고 힙을 할당하지 않는 한 일종의 힙 할당을 피할 수 있다고 상상합니다. LINQ가 생성 할 수있는 모든 상태 시스템은 Bare Object로 선언되지 않은 Select'd 결과에 대한 필드를 사용하므로 KVP 결과가 상자로 표시됩니다. 물론 found == null 조건을 재 작업해야합니다 (발견 된 KVP 값이므로). 아마도 DefaultIfEmpty () 또는 KVP<T, int?>(nullable index) 사용
kornman00

1
좋은 구현이지만, 내가 추가 할 것을 제안하는 것은 obj가 구현되는지 확인하는 IList<T>것이고, 그렇다면 유형별 최적화가있는 경우를 대비하여 IndexOf 메소드를 연기하십시오.
Josh

16

내가 현재하고있는 방법은 이미 제안 된 것보다 약간 짧으며 내가 알 수있는 한 원하는 결과를 얻습니다.

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

약간 어수선하지만 작업을 수행하고 상당히 간결합니다.


6
작은 컬렉션에도 적용되지만 "haystack"에 백만 개의 항목이 있다고 가정합니다. ToList ()를 수행하면 모든 백만 요소를 반복하여 목록에 추가합니다. 그런 다음 목록을 검색하여 일치하는 요소의 색인을 찾습니다. 이것은 목록이 너무 커지면 예외를 던질 가능성뿐만 아니라 매우 비효율적입니다.
esteuart

3
@esteuart 확실히-사용 사례에 맞는 접근 방식을 선택해야합니다. 모든 솔루션에 맞는 단일 크기가 있는지 의심 스럽기 때문에 코어 라이브러리에 구현이없는 것 같습니다.
Mark Watts

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

7

가장 좋은 옵션은 다음과 같이 구현하는 것입니다.

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

잘하면 이것은 누군가를 돕는다.


1
어쩌면 내가 뭔가를 잃어 버렸을 수도 있지만 왜 GetEnumeratorand MoveNext보다는 오히려 foreach?
Josh Gallagher

1
짧은 답변? 능률. 긴 대답 : msdn.microsoft.com/en-us/library/9yb8xew9.aspx
MaxOvrdrv

2
IL을 보면 성능 ​​차이는 열거자가 구현하는 경우 열거자를 foreach호출 Dispose한다는 것 IDisposable입니다. ( stackoverflow.com/questions/4982396/… 참조 )이 답변의 코드는 호출 결과 GetEnumerator가 일회용인지 아닌지를 모르므로 동일하게 수행해야합니다. 그 시점에서 나는 여전히 perf 혜택이 있는지 확실하지 않지만, 목적이 나에게 뛰어나지 않은 여분의 IL이 있었지만!
Josh Gallagher

@JoshGallagher 나는 foreach와 for (i) 사이의 perf 이점에 대해 약간의 연구를 한 적이 있으며 for (i)를 사용하는 주된 이점은 객체를 다시 작성하거나 전달하는 대신 ByRefs 객체를 제 위치에 놓았다는 것입니다 다시 ByVal. MoveNext와 foreach에도 동일하게 적용되지만 그에 대해서는 확실하지 않습니다. 어쩌면 그들은 모두 사용 ByVal의 ...
MaxOvrdrv

2
이 블로그 (읽기 blogs.msdn.com/b/ericlippert/archive/2010/09/30/... ) 그가 언급되는은 "반복자 루프"는 것을있을 수 있습니다 foreach루프의 특정 경우에 경우에 T값 유형이므로 while 루프를 사용하여 box / unbox 작업을 저장할 수 있습니다. 그러나 이것은 귀하의 답변 버전에서 얻은 IL에 의해 제공되지 않습니다 foreach. 그래도 반복자의 조건부 처리가 중요하다고 생각합니다. 포함하도록 답변을 수정할 수 있습니까?
Josh Gallagher

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

이를 수행하는 다른 방법은 다음과 같습니다. public static int IndexOf <T> (이 IEnumerable <T> 목록, T 항목) {int e = list.Select ((x, index) => EqualityComparer <T> .Default.Equals ( 항목, x) ≤ x + 1 : -1) .FirstOrDefault (x => x> 0); return (e == 0)? -1 : e-1); }
Anu Thomas Chandy

"추가 개체를 만들지 않았습니다". Linq는 실제로 백그라운드에서 객체를 생성하므로 이것이 완전히 올바르지 않습니다. 모두 source.Wheresource.DefaultIfEmpty예를 들어 생성합니다 IEnumerable각각.
Martin Odhelius

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)에 대해, 최악의 시나리오는 원하는 항목이 끝났습니다. 수행하는 것보다 2 배 빠릅니다 ToList().FindIndex(). 원하는 아이템이 중간에 ~ 4 배 빠릅니다.

소규모 컬렉션 (~ 10,000)의 경우 조금 더 빠른 것 같습니다

여기 내가 어떻게 그것을 테스트했는지 https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e


1

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

나는이 방법이 가장 빠르고 간단 할 수 있다고 생각합니다. 그러나 아직 성능을 테스트하지 않았습니다.


0

사실 이후 인덱스를 찾는 대안은 Linq GroupBy () 메서드를 사용하는 것과 다소 비슷한 Enumerable을 래핑하는 것입니다.

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

훌륭한 기준선. IsEven, IsOdd, IsFirst 및 IsLast 멤버를 추가하면 도움이됩니다.
JJS

0

다음과 같이 확장 기능 (프록시 기능)으로 정말 멋질 수 있습니다.

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();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.