IEnumerable'da bir öğenin dizinini nasıl alabilirim?


144

Bunu ben yazdım:

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

Ama zaten var olup olmadığını bilmiyorum, değil mi?


4
Bir Maxyaklaşımla ilgili sorun , a: aramaya devam etmesidir ve b: kopyalar olduğunda son dizini döndürür (insanlar genellikle ilk dizini bekler)
Marc Gravell

1
geekswithblogs.net 4 çözümü ve performanslarını karşılaştırır. ToList()/FindIndex()Hüner en üstün performans
nixda

Yanıtlar:


51

IEnumerable olarak şeyleri çıkarmanın tüm amacı, böylece içerik üzerinde tembel bir şekilde tekrarlayabilirsiniz. Bu nedenle, gerçekten bir indeks kavramı yoktur. Yaptığınız şey gerçekten bir IEnumerable için pek mantıklı değil. Dizine göre erişimi destekleyen bir şeye ihtiyacınız varsa, bunu gerçek bir listeye veya koleksiyona koyun.


8
Şu anda bu iş parçacığının karşısına geldim çünkü IEnumerable <> türü için genel IList <> sarmalayıcısını kullanıyorum çünkü IEnumerable <> nesnelerimi yalnızca IList tipi veri kaynaklarını destekleyen üçüncü taraf bileşenlerle kullanmak için. IEnumerable nesnesi içindeki bir öğenin bir dizinini almaya çalışmanın, çoğu durumda yanlış yapılan bir şeyin işareti olduğuna inanıyorum, bu dizini bulduktan sonra, sadece dizini bulmak için bellekte büyük bir koleksiyon çoğaltmayı yener. zaten bir IEnumerable varsa tek bir eleman.
jpierson

215
-1 nedeni: Bir dizinden bir dizin almak istemenizin meşru nedenleri vardır IEnumerable<>. Bütün "bunu yapmalısın" dogmasını almıyorum.
John Alexiou

78
@ Ja72 ile aynı fikirde; Eğer endekslerle uğraşmamalısınız, IEnumerableo zaman var Enumerable.ElementAtolmazdı. IndexOfbasitçe tersidir - aleyhinde olan herhangi bir argüman eşit olarak uygulanmalıdır ElementAt.
Kirk Woll

7
Açıkçası, C # IIndexableEnumerable kavramını özlüyor. bu C ++ STL terminolojisinde "rastgele erişilebilir" bir kavramın karşılığıdır.
v.oddou

14
((x, i) => ...) gibi aşırı yüklemelere sahip uzantılar, bu dizinlerin mevcut olması gerektiği anlamına geliyor
Michael

126

Bilgeliği sorgulardım ama belki de:

source.TakeWhile(x => x != value).Count();

( EqualityComparer<T>.Defaultgerekiyorsa taklit etmek için kullanılır !=) - ancak bulunamazsa -1'e dönmek için izlemeniz gerekir ... bu yüzden belki de uzun yoldan yapın

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 "bilgeliği sorgulamak" için. 10 üzerinden 9 kez ilk etapta muhtemelen kötü bir fikir.
Joel Coehoorn

Açık döngü çözümü ayrıca Select (). Max () çözümünden 2 kat daha hızlı (en kötü durumda) çalışır.
Steve Guidi

1
TakeWhile olmadan öğeleri lambda ile sayabilirsiniz - bir döngü kaydeder: source.Count (x => x! = Value);
Kamarey

10
@Kamarey - hayır, bu farklı bir şey yapıyor. TakeWhile hata aldığında durur ; Count (yüklem) eşleşenleri döndürür. Birincisi bir bayan olsaydı ve her şey doğruysa, TakeWhile (pred) .Count () 0 bildirir; Sayım (pred) n-1 rapor edecektir.
Marc Gravell

1
TakeWhileakıllı! Bununla birlikte Count, standart davranıştan sapma olan bir öğe yoksa bunun geri döndüğünü unutmayın .
nawfal

27

Ben böyle uygulamak istiyorum:

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
Aslında çok sevimli, +1! Ekstra nesneler içerir, ancak nispeten ucuz olmalıdırlar (GEN0), bu yüzden büyük bir sorun değil. ==İşi gerekebilir?
Marc Gravell

1
Gerçek LINQ tarzında IEqualityComparer aşırı yüklendi. ;)
dahlbyk

1
Sanırım demek istedin ... comparer.Equals (xa, değer) =)
Marc

Select ifadesi, daha sonra işlenen birleşik sonucu döndürdüğünden, açıkça, .NET impl yığını üzerinde değer türlerini ayırdığı ve herhangi bir yığın tahsisinden kaçınabilmenizi sağlayan KeyValuePair değer türünü açıkça kullanacağınızı hayal ediyorum. LINQ'nun oluşturabileceği herhangi bir durum makinesi, Select'd sonucu için çıplak bir Nesne olarak bildirilmeyen bir alan kullanır (böylece KVP sonucunun kutulanmasına neden olur). Elbette, bulunan == null koşulunu yeniden işlemeniz gerekir (çünkü artık bir KVP değeri olacaktır). Belki DefaultIfEmpty () veya KVP<T, int?>(
nulllable

1
Güzel bir uygulama olsa da, eklemeyi önerdiğim bir şey IList<T>, obj'in uygulanıp uygulanmadığını görmek için bir kontroldür .
Josh

16

Şu anda bunu yapmanın yolu zaten önerilenlerden biraz daha kısa ve söyleyebildiğim kadarıyla istenen sonucu veriyor:

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

Biraz hantal, ama işi yapıyor ve oldukça özlü.


6
Bu küçük koleksiyonlar için işe yarar olsa da, "samanlıkta" bir milyon öğeniz olduğunu varsayalım. Bunun üzerinde bir ToList () yapmak, bir milyon öğenin tümünü yineleyecek ve bir listeye ekleyecektir. Ardından, eşleşen öğenin dizinini bulmak için listede arama yapar. Bu liste son derece verimsiz olur ve liste çok büyürse istisna fırlatma olasılığı da vardır.
esteuart

3
@esteuart Kesinlikle - kullanım durumunuza uygun bir yaklaşım seçmeniz gerekir. Tüm çözümlere uyan tek bir boyut olduğundan şüpheliyim, bu yüzden muhtemelen çekirdek kütüphanelerinde bir uygulama yoktur.
Mark Watts

8

Konumu yakalamanın en iyi yolu şudur: FindIndexBu işlev yalnızca şunlar için kullanılabilir:List<>

Misal

int id = listMyObject.FindIndex(x => x.Id == 15); 

Numaralandırıcı veya diziniz varsa bu yolu kullanın

int id = myEnumerator.ToList().FindIndex(x => x.Id == 15); 

veya

 int id = myArray.ToList().FindIndex(x => x.Id == 15); 

7

Bence en iyi seçenek böyle uygulamaktır:

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

Ayrıca anonim nesne oluşturmaz


5

Oyuna biraz geç biliyorum, ama son zamanlarda yaptığım da bu. Sizinkinden biraz farklıdır, ancak programcının eşitlik işleminin ne olması gerektiğini dikte etmesine izin verir (yüklem). Hangi i farklı türlerle uğraşırken çok yararlı buluyorum, çünkü o zaman nesne türü ne olursa olsun bunu yapmak ve genel <T>eşitlik operatörü inşa genel bir yolu var .

Ayrıca çok küçük bir bellek alanına sahiptir ve çok, çok hızlı / verimlidir ... eğer bunu önemsiyorsanız.

Daha da kötüsü, bunu uzantı listenize eklemeniz yeterlidir.

Her neyse ... işte burada.

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

Umarım bu birine yardımcı olur.


1
Belki bir şeyler eksik, ama neden ediyorum GetEnumeratorve MoveNextyerine sadece daha foreach?
Josh Gallagher

1
Kısa cevap? Verimlilik. Uzun cevap: msdn.microsoft.com/en-us/library/9yb8xew9.aspx
MaxOvrdrv

2
IL'ye bakıldığında, performans farkının, eğer uygulayıcıyı numaralandırıcıya foreachçağıracağı Disposeolduğu görülmektedir IDisposable. (Bkz. Stackoverflow.com/questions/4982396/… ) Bu yanıttaki kod, arama sonucunun GetEnumeratortek kullanımlık olup olmadığını bilmediğinden, aynı şeyi yapmalıdır. Bu noktada hâlâ mükemmel bir fayda olduğu konusunda emin değilim, ancak amacı bana sıçramayan ekstra bir IL vardı!
Josh Gallagher

@JoshGallagher Bir kez daha foreach ve (i) arasındaki mükemmel faydalar konusunda biraz araştırma yaptım ve (i) için kullanmanın ana yararı, nesneyi yeniden oluşturmak / geçirmek yerine yerinde ByRefs olmasıdır. geri ByVal. Aynı şeyi MoveNext ve foreach için de geçerlidir, ama bundan emin değilim. Belki ikisi de ByVal kullanıyor ...
MaxOvrdrv

2
Bu blogu okumak ( blogs.msdn.com/b/ericlippert/archive/2010/09/30/… ), sözünü ettiği "yineleyici döngüsü" bir foreachdöngü olabilir, bu durumda Tbir değer türü olarak while döngüsünü kullanarak bir kutu / kutu açma işlemini kaydediyor olabilir. Ancak, bu, cevabınızın bir versiyonundan aldığım IL tarafından doğmaz foreach. Yine de yineleyicinin koşullu bertarafının önemli olduğunu düşünüyorum. Cevabı içerecek şekilde değiştirebilir misiniz?
Josh Gallagher

5

Birkaç yıl sonra, ancak bu Linq kullanır, bulunmazsa -1 döndürür, fazladan nesne oluşturmaz ve [IEnumerable'ın tamamında yinelemenin aksine] bulunduğunda kısa devre yapmalıdır :

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' nerede:

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

Bunu yapmanın başka bir yolu da olabilir: public static int IndexOf <T> (bu IEnumerable <T> listesi, T öğesi) {int e = list.Select ((x, index) => EqualityComparer <T> .Default.Equals ( madde, x)? x + 1: -1) .İlk Veya Varsayılan (x => x> 0); dönüş (e == 0)? -1: e-1); }
Anu Thomas Chandy

msgstr "fazladan nesne yaratmaz". Linq aslında arka planda nesneler yaratacaktır, böylece bu tamamen doğru değildir. Her ikisi de source.Whereve source.DefaultIfEmptyörneğin IEnumerableher birini oluşturacaktır .
Martin Odhelius

1

Bugün cevaplar bulmak için tökezledi ve ben versiyonumu listeye eklemek istiyorum düşündüm (Hiçbir ceza amaçlanan). C # 6.0 null koşullu işleci kullanır

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;

Bazı 'eski atların yarışı' (test) ve büyük koleksiyonlar (~ 100.000) için yaptım, istediğiniz öğenin sonunda en kötü durum senaryosu, bu yapmaktan 2 kat daha hızlı ToList().FindIndex(). İstediğiniz Öğe ortada ise ~ 4x daha hızlı.

Daha küçük koleksiyonlar için (~ 10.000) sadece çok daha hızlı görünüyor

Heres nasıl test ettim https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e


1

@Marc Gravell yanıtını kullanarak, aşağıdaki yöntemi kullanmanın bir yolunu buldum:

source.TakeWhile(x => x != value).Count();

Öğe bulunamadığında -1 almak için:

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

Sanırım bu yol hem en hızlı hem de daha basit olabilir. Ancak, henüz performansları test etmedim.


0

Gerçekten sonra dizini bulmanın bir alternatifi, Linq GroupBy () yöntemini kullanmaya benzer şekilde, Numaralandırılabilir'i sarmaktır.

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

Hangi kullanım örneği verir:

var items = new[] {1, 2, 3};
var indexedItems = items.ToIndexed();
foreach (var item in indexedItems)
{
    Console.WriteLine("items[{0}] = {1}", item.Index, item.Value);
}

büyük taban çizgisi. IsEven, IsOdd, IsFirst ve IsLast üyelerini de eklemek yararlıdır.
JJS

0

Bu, bir uzantıyla (proxy olarak işlev görür) gerçekten harika olabilir, örneğin:

collection.SelectWithIndex(); 
// vs. 
collection.Select((item, index) => item);

Bu, otomatik olarak bu Indexözellik üzerinden erişilebilen koleksiyona dizinler atar .

Arayüz:

public interface IIndexable
{
    int Index { get; set; }
}

Özel uzantı (muhtemelen en çok EF ve DbContext ile çalışmak için yararlıdır):

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();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.