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
現在、Inumerable <>タイプのジェネリックIList <>ラッパーを実装して、タイプIListのデータソースのみをサポートするサードパーティコンポーネントでIEnumerable <>オブジェクトを使用しているため、このスレッドに遭遇しました。IEnumerableオブジェクト内の要素のインデックスを取得しようとすると、ほとんどの場合、インデックスを見つけるためだけに、メモリ内の大規模なコレクションを再現するよりも、そのようなインデックスを見つけるのが不適切な場合の兆候であることに同意しますIEnumerableが既にある場合は、単一の要素の。
jpierson 2009

215
-1原因:からインデックスを取得する正当な理由がありますIEnumerable<>。私は「あなたはこれをやるだろう」というドグマ全体を購入しません。
John Alexiou

78
@ ja72に同意する。あなたがインデックスに対処すべきではない場合IEnumerable、その後Enumerable.ElementAtは存在しないでしょう。 IndexOfは単に逆ElementAtです。これに対する引数はすべてに等しく適用される必要があります。
カークウォル2009

7
C#はIIndexableEnumerableの概念を見逃しています。これは、C ++ STLの用語では、「ランダムにアクセス可能な」概念と同等です。
v.oddou 2013年

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、2009

明示的なループソリューションは、Select()。Max()ソリューションよりも(最悪の場合)2倍速く実行されます。
スティーブギディ

1
TakeWhileを使わずにラムダで要素を数えるだけで、ループを1つ節約できます。source.Count(x => x!= value);
カマレイ2009年

10
@カマリー-いいえ、それは何か違うことをします。TakeWhile は、失敗すると停止します。Count(述語)は、一致するものを返します。つまり、最初がミスで他のすべてが真の場合、TakeWhile(pred).Count()は0を報告します。Count(pred)はn-1を報告します。
Marc Gravell

1
TakeWhile賢いです!ただしCount、標準の動作からの逸脱である要素が存在しない場合はこれが返されます。
nawfal 2017年

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 2009

1
私はあなたが言うことを意味すると思います... comparer.Equals(xa、value)=)
Marc

Select式が結合された結果を返し、それが処理されるので、KeyValuePair値タイプを明示的に使用すると、.NET実装がスタックに値タイプを割り当て、 LINQが生成する可能性のあるステートマシンは、ベアオブジェクトとして宣言されていないSelect'd結果のフィールドを使用します(そのため、KVP結果がボックス化されます)。もちろん、found == null条件を修正する必要があります(foundがKVP値になるため)。おそらくDefaultIfEmpty()またはKVP<T, int?>(nullable index)を使用している
kornman00

1
いい実装ですが、追加することをお勧めしますが、objが実装されIList<T>ているかどうかを確認し、実装されている場合は、型固有の最適化がある場合に備えて、そのIndexOfメソッドを使用します。
Josh

16

私が現在これをやっている方法は、すでに提案されている方法よりも少し短く、私が知る限り、望ましい結果が得られます:

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

それは少し不格好ですが、それは仕事をし、かなり簡潔です。


6
これは小さなコレクションで機能しますが、「干し草」に100万のアイテムがあるとします。その上でToList()を実行すると、100万個すべての要素が反復処理され、リストに追加されます。次に、リストを検索して、一致する要素のインデックスを見つけます。これは非常に非効率的であり、リストが大きくなりすぎると例外がスローされる可能性があります。
esteuart 2015

3
@esteuart間違いなく-ユースケースに適したアプローチを選択する必要があります。すべてのソリューションに適合する1つのサイズがあるとは思えません。そのため、コアライブラリに実装がありません。
マークワッツ

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
多分私は何かが欠けていますが、なぜ GetEnumeratorMoveNextいうだけよりもforeach
Josh Gallagher、

1
短い答え?効率。長い答え: msdn.microsoft.com/en-us/library/9yb8xew9.aspx
MaxOvrdrv

2
ILを見ると、パフォーマンスの違いは、a foreachDispose実装する場合に列挙子を呼び出すことIDisposableです。(stackoverflow.com/questions/4982396/…を参照)この回答のコードは、呼び出しの結果がGetEnumerator使い捨てであるかどうかがわからないため、同じようにする必要があります。その時点では、まだパフォーマンス上の利点があるかどうかはわかりませんが、目的が私に飛び出さないILが余分にありました!
ジョシュギャラガー

@JoshGallagher foreachとfor(i)の間のパフォーマンス上の利点について少し前に調査しましたが、for(i)を使用する主な利点は、オブジェクトを再作成/渡すのではなく、インプレースでオブジェクトをByRefすることでした戻るByVal。同じことがMoveNextとforeachに当てはまると思いますが、それについてはわかりません。多分彼らは両方ともByValを使用しています...
MaxOvrdrv 2015年

2
このブログ(blogs.msdn.com/b/ericlippert/archive/2010/09/30/…)を読んでいると、彼が参照している「イテレータループ」がループである可能性がありforeachます。T値型であるため、whileループを使用してボックス/ボックス解除操作を保存している可能性があります。しかし、これは私がであなたの回答のバージョンから得た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>(this IEnumerable <T> list、T item){int e = list.Select((x、index)=> EqualityComparer <T> .Default.Equals( item、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

事後にインデックスを見つける代わりに、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);
}

素晴らしいベースライン。IsEven、IsOdd、IsFirst、IsLastのメンバーも追加すると便利です。
JJS 2017年

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.