web-dev-qa-db-fra.com

Comment obtenir l'index d'un élément dans un IEnumerable?

J'ai écrit ceci:

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

Mais je ne sais pas s'il existe déjà, n'est-ce pas?

117
Jader Dias

Il est important de faire en sorte que les choses soient comme IEnumerable pour pouvoir parcourir par la suite le contenu. En tant que tel, il n'y a pas vraiment de concept d'index. Ce que vous faites n’a vraiment aucun sens pour un IEnumerable. Si vous avez besoin de quelque chose qui prenne en charge l'accès par index, placez-le dans une liste ou une collection réelle.

45
Scott Dorman

Je mettrais en doute la sagesse, mais peut-être:

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

(en utilisant EqualityComparer<T>.Default pour émuler != si nécessaire) - mais vous devez faire attention à retourner -1 si non trouvé ... alors peut-être juste le faire le long chemin

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

Je le mettrais en œuvre comme ceci:

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

La façon dont je le fais actuellement est un peu plus courte que celles déjà suggérées et, autant que je sache, donne le résultat souhaité:

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

C'est un peu maladroit, mais il fait le travail et est assez concis.

11
Mark Watts

Je pense que la meilleure option est d'implémenter comme ceci:

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

Cela ne créera pas non plus d'objet anonyme

6
Axente Adrian

Quelques années plus tard, mais cela utilise Linq, renvoie -1 s'il n'est pas trouvé, ne crée pas d'objets supplémentaires et devrait court-circuiter s'il est trouvé [par opposition à une itération sur l'ensemble de 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);
}

Où 'FirstOr' est:

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

Un peu tard dans le jeu, je sais ... mais c'est ce que j'ai fait récemment. Il est légèrement différent du vôtre, mais permet au programmeur de dicter ce que l'opération d'égalité doit être (prédicat). Ce que je trouve très utile quand il s’agit de types différents, car j’ai alors une façon générique de le faire quel que soit le type d’objet et <T> construit dans un opérateur d’égalité.

Il a également une très petite empreinte mémoire, et est très, très rapide/efficace ... si vous vous souciez de cela.

Au pire, vous ajouterez simplement ceci à votre liste d'extensions.

Quoi qu'il en soit ... le voici.

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

Espérons que cela aide quelqu'un.

5
MaxOvrdrv

La meilleure façon d’attraper la position est de FindIndex Cette fonction n’est disponible que pour List<>

Exemple

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

Si vous avez un énumérateur ou un tableau, utilisez cette méthode

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

ou

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

Je suis tombé sur cette question aujourd'hui dans une recherche de réponses et j'ai pensé que j'ajouterais ma version à la liste (Sans jeu de mots). Il utilise l'opérateur conditionnel nul de 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;

J'ai fait quelques "courses de vieux chevaux" (tests) et pour les grandes collections (~ 100 000), dans le pire des cas, l'élément que vous voulez est à la fin, c'est 2x plus rapide que de faire ToList().FindIndex(). Si l'élément que vous voulez est au milieu, son ~ 4x est plus rapide.

Pour les plus petites collections (~ 10 000), cela ne semble guère plus rapide

Voici comment je l'ai testé https://Gist.github.com/insulind/16310945247fcf13ba186a45734f254e

1
Dave

En utilisant la réponse de @Marc Gravell, j'ai trouvé un moyen d'utiliser la méthode suivante:

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

afin d'obtenir -1 lorsque l'élément est introuvable:

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

Je suppose que cette façon pourrait être à la fois la plus rapide et la plus simple. Cependant, je n'ai pas encore testé les performances.

1
Davide Cannizzo

Une alternative à la recherche de l'index après le fait est d'encapsuler Enumerable, un peu similaire à l'utilisation de la méthode 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();
    }
}

Ce qui donne un cas d'utilisation de:

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

Cela peut être vraiment cool avec une extension (fonctionnant comme un proxy), par exemple:

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

Ce qui assignera automatiquement les index à la collection accessible via cette propriété Index.

Interface:

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

Extension personnalisée (probablement la plus utile pour travailler avec EF et 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