it-swarm.com.de

Wie erhalte ich den Index eines Elements in einem IEnumerable?

Ich habe das geschrieben:

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

Aber ich weiß nicht, ob es schon existiert, oder?

117
Jader Dias

Der springende Punkt, Dinge als IEnumerable herauszuholen ist, damit Sie den Inhalt träge durchlaufen können. Als solches gibt es kein wirklich ein Konzept eines Index. Was Sie tun, macht für einen IEnumerable wirklich keinen Sinn. Wenn Sie etwas benötigen, das den Zugriff durch Index unterstützt, legen Sie es in eine tatsächliche Liste oder Sammlung.

45
Scott Dorman

Ich würde die Weisheit in Frage stellen, aber vielleicht:

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

(mit EqualityComparer<T>.Default zum Emulieren von !=, falls erforderlich) - aber Sie müssen darauf achten, -1 zurückzugeben, falls dies nicht der Fall ist

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

Ich würde es so umsetzen:

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

Die Art, wie ich dies derzeit mache, ist etwas kürzer als die bereits vorgeschlagenen und soweit ich das beurteilen kann, ergibt sich das gewünschte Ergebnis:

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

Es ist ein bisschen klobig, aber es macht den Job und ist ziemlich knapp.

11
Mark Watts

Ich denke, die beste Option ist, so zu implementieren:

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

Das anonyme Objekt wird auch nicht erstellt

6
Axente Adrian

Ein paar Jahre später verwendet Linq, gibt -1 zurück, wenn es nicht gefunden wird, erstellt keine zusätzlichen Objekte und sollte schließt einen Kurzschluss, wenn er gefunden wird [im Gegensatz zur Iteration über das gesamte 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);
}

Wo 'FirstOr' ist:

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

Ein bisschen spät im Spiel, ich weiß ... aber das habe ich kürzlich getan. Es unterscheidet sich geringfügig von Ihrem eigenen, erlaubt dem Programmierer jedoch, zu bestimmen, was die Gleichheitsoperation sein muss (Prädikat). Was ich beim Umgang mit verschiedenen Typen sehr nützlich finde, da ich eine generische Methode dafür habe, unabhängig vom Objekttyp und dem <T> eingebauten Gleichheitsoperator.

Es hat auch einen sehr kleinen Speicherbedarf und ist sehr, sehr schnell/effizient ... wenn Sie sich darum kümmern.

Im schlimmsten Fall fügen Sie dies einfach zu Ihrer Liste der Erweiterungen hinzu.

Wie auch immer ... hier ist es.

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

Hoffentlich hilft das jemandem.

5
MaxOvrdrv

Die Position kann am besten mit FindIndex erfasst werden. Diese Funktion ist nur für List<> verfügbar.

Beispiel

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

Wenn Sie einen Enumerator oder ein Array haben, verwenden Sie diesen Weg

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

oder

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

Heute bin ich bei der Suche nach Antworten auf dieses Thema gestoßen und ich dachte, ich würde meine Version der Liste hinzufügen (Kein Wortspiel beabsichtigt). Es verwendet den bedingten Null-Operator von c # 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;

Ich habe ein paar Rennen mit den alten Pferden gemacht (Tests) und für große Sammlungen (~ 100.000). Im schlimmsten Fall, dass der gewünschte Gegenstand am Ende ist, ist 2x schneller als ToList().FindIndex(). Wenn sich das gewünschte Objekt in der Mitte befindet, ist es ~ 4x schneller.

Bei kleineren Sammlungen (~ 10.000) scheint es nur unwesentlich schneller zu sein

Hier habe ich es getestet https://Gist.github.com/insulind/16310945247fcf13ba186a45734f254e

1
Dave

Unter Verwendung der Antwort von @Marc Gravell habe ich eine Möglichkeit gefunden, die folgende Methode anzuwenden:

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

um -1 zu erhalten, wenn der Gegenstand nicht gefunden werden kann:

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

Ich denke, dieser Weg könnte sowohl der schnellste als auch der einfachere sein. Allerdings habe ich noch keine Performances getestet.

1
Davide Cannizzo

Eine Alternative zum Finden des Index nach der Tatsache besteht darin, das Enumerable-Element zu umschließen, ähnlich wie bei der Verwendung der Linq GroupBy () -Methode.

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

Was gibt einen Anwendungsfall von:

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

Das kann mit einer Erweiterung (die als Proxy funktioniert) wirklich cool werden, zum Beispiel:

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

Dadurch werden der Sammlung automatisch Indizes zugewiesen, die über diese Index-Eigenschaft zugänglich sind.

Schnittstelle:

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

Benutzerdefinierte Erweiterung (wahrscheinlich am nützlichsten für die Arbeit mit EF und 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
jom