web-dev-qa-db-ja.com

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

しかし、それが既に存在するかどうかはわかりませんよね?

131
Jader Dias

IEnumerableとして物事を引き出すことの全体的なポイントは、コンテンツを遅延的に反復できることです。そのため、実際にインデックスの概念はありません。あなたがしていることは、IEnumerableにとってはあまり意味がありません。インデックスによるアクセスをサポートするものが必要な場合は、実際のリストまたはコレクションに入れてください。

49
Scott Dorman

私は知恵に疑問を呈しますが、おそらく:

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;
}
111
Marc Gravell

次のように実装します。

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

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

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

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

14
Mark Watts

最適なオプションは次のように実装することです。

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
Axente Adrian

位置をキャッチする最良の方法は、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); 
5
daniele3004

数年後、これはLinqを使用し、見つからない場合は-1を返し、余分なオブジェクトを作成せず、見つかった場合はshouldショートサーキット(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);
}
5
Greg

ゲームの少し遅れて、私は知っている...しかし、これは私が最近やったことです。それはあなたのものとわずかに異なりますが、プログラマーが等価操作に必要なものを指示することができます(述語)。異なる型を扱うときに非常に便利だと思うのは、オブジェクト型や<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;
 }

これが誰かの助けになることを願っています。

5
MaxOvrdrv

今日、これに出くわした答えを探して、リストに自分のバージョンを追加しようと思いました(しゃれはありません)。 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)、あなたが望むアイテムが最後にある最悪のシナリオでは、これは2xToList().FindIndex()を実行するよりも高速です。必要なアイテムが中央にある場合、その〜4xは高速です。

より小さいコレクション(〜10,000)の場合、わずかに速いだけのようです

私がそれをテストした方法https://Gist.github.com/insulind/16310945247fcf13ba186a45734f254e

1
Dave

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

この方法は、最速かつ簡単な方法であると思います。ただし、パフォーマンスはまだテストしていません。

1
Davide Cannizzo

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

これは、拡張機能(プロキシとして機能する)を使用すると非常にクールになります。次に例を示します。

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();
0
Yom S.