web-dev-qa-db-fra.com

LINQ pour trouver les index de tableau d'une valeur

En supposant que j'ai le tableau de chaînes suivant:

string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"}

Est-il possible d'utiliser LINQ pour obtenir une liste d'index correspondant à une chaîne?

Par exemple, je voudrais rechercher la chaîne "avg" et obtenir une liste contenant 

2, 4

ce qui signifie que "avg" peut être trouvé à str [2] et str [4].

44
Dan Dinu

.Select a une surcharge peu utilisée qui produit un index. Vous pouvez l'utiliser comme ceci:

str.Select((s, i) => new {i, s})
    .Where(t => t.s == "avg")
    .Select(t => t.i)
    .ToList()

Le résultat sera une liste contenant 2 et 4.

Documentation ici

87
recursive

Vous pouvez le faire comme ça:

str.Select((v,i) => new {Index = i, Value = v}) // Pair up values and indexes
   .Where(p => p.Value == "avg") // Do the filtering
   .Select(p => p.Index); // Keep the index and drop the value

La clé consiste à utiliser la surcharge de Select qui fournit l’index actuel à votre foncteur.

14
dasblinkenlight

Vous pouvez utiliser la surcharge de Enumerable.Select qui transmet l'index, puis utiliser Enumerable.Where sur un type anonyme:

List<int> result = str.Select((s, index) => new { s, index })
                      .Where(x => x.s== "avg")
                      .Select(x => x.index)
                      .ToList();

Si vous voulez juste trouver le premier/dernier index, vous avez aussi les méthodes intégrées List.IndexOf et List.LastIndexOf:

int firstIndex = str.IndexOf("avg");
int lastIndex = str.LastIndexOf("avg");

(ou vous pouvez utiliser cette surcharge qui prend un index de départ pour spécifier la position de départ)

6
Rango

Bien que vous puissiez utiliser une combinaison de Select et Where, ceci est probablement un bon candidat pour créer votre propre fonction:

public static IEnumerable<int> Indexes<T>(IEnumerable<T> source, T itemToFind)
{
    if (source == null)
        throw new ArgumentNullException("source");

    int i = 0;
    foreach (T item in source)
    {
        if (object.Equals(itemToFind, item))
        {
            yield return i;
        }

        i++;
    }
}
2
Servy

Tout d'abord, votre code ne parcoure pas la liste deux fois, il le répète seulement une fois.

Cela dit, votre choix ne fait qu’obtenir une séquence de tous les index; cela se fait plus facilement avec Enumerable.Range: 

 var result = Enumerable.Range(0, str.Count)
                 .Where(i => str[i] == "avg")
                 .ToList();

Comprendre pourquoi la liste n’est pas réellement itérée deux fois demande de s’habituer. Je vais essayer de donner une explication de base.

Vous devez penser à la plupart des méthodes LINQ, telles que Select et Where, en tant que pipeline. Chaque méthode fait un peu de travail. Dans le cas de Select, vous lui donnez une méthode, qui indique essentiellement: "Chaque fois que quelqu'un me demande mon prochain article, je vais d'abord demander à ma séquence d'entrée un article, puis utiliser la méthode qu'il me faut pour le convertir en autre chose, et donnez ensuite cet objet à celui qui m'utilise. " Là où, plus ou moins, on dit, "chaque fois que quelqu'un me demande un élément, je demande ma séquence d'entrée à un élément, si la fonction dit qu'il est bon, je le transmettrai, sinon je continuerai à demander des éléments jusqu'à ce que j'en ai un qui passe. "

Ainsi, lorsque vous les enchaînez, ce qui se passe est que ToList demande le premier élément, il se place dans Où trouver comme premier élément, Où passe dans Sélectionner et lui demande le premier élément, Select sélectionne le premier élément. article. La liste fournit alors son premier élément. Sélectionnez puis transforme cet élément en ce dont il a besoin pour cracher (dans ce cas, uniquement l'int 0) et le donne à Où. Où prend cet élément et exécute sa fonction qui détermine sa véracité et envoie 0 dans ToList, qui l'ajoute à la liste. Tout cela se passe ensuite 9 fois de plus. Cela signifie que Select finira par demander chaque élément de la liste exactement une fois et transmettra chacun de ses résultats directement à Where, qui transmettra les résultats qui "passent le test" directement à ToList, qui les stocke dans une liste. . Toutes les méthodes LINQ sont soigneusement conçues pour n'itérer qu'une seule fois la séquence source (lorsqu'elles sont itérées une fois).

Notez que, bien que cela vous semble compliqué au début, il est en fait assez facile pour l'ordinateur de faire tout cela. En réalité, il n’est pas aussi intensif que cela puisse paraître au premier abord.

1
BrainCoder

Vous avez besoin d'un opérateur de sélection et où combiné, comparant à la réponse acceptée, ce sera moins cher, car il ne nécessitera pas d'objets intermédiaires:

public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> filter, Func<TSource, int, TResult> selector)
        {
            int index = -1;
            foreach (var s in source)
            {
                checked{ ++index; }
                if (filter(s))
                    yield return selector(s, index);
            }
        }
0
Mikl X