2012-11-08 5 views
40

En supposant que j'ai le tableau de chaînes suivantes:LINQ pour trouver les indices de tableau d'une valeur

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

est-il possbile d'utiliser LINQ pour obtenir une liste des index qui correspondent à une chaîne?

À titre d'exemple, je voudrais rechercher la chaîne "moyenne" et obtenir une liste contenant

2, 4

ce qui signifie que "avg" se trouve à l'str [ 2] et str [4].

Répondre

72

.Select a une surcharge rarement 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 here

+6

Notez également que ['Where'] (http://msdn.microsoft.com/en-us/library/bb549418.aspx) fournit cette surcharge. –

+0

super duper génial merci pour ça! Code – Jonesopolis

+0

est un peu plus intelligent avec Où - merci Tim! – Sven

13

Vous pouvez le faire comme ceci:

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é L'étape utilise the overload of Select qui fournit l'index actuel à votre foncteur.

6

Vous pouvez utiliser la surcharge de Enumerable.Select qui passe l'index, puis utilisez 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 indice, vous avez également les méthodes BUILTIN List.IndexOf et List.LastIndexOf:

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

(ou vous pouvez utiliser this overload qui prennent un index de départ pour définir la position de départ)

2

Alors que vous pouvez utiliser une combinaison de Select et Where, cela est probablement un bon candidat pour faire 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++; 
    } 
} 
+0

Belle alternative. Je dirais qu'un «itemToFind» null devrait être un cas d'utilisation légal, mais avoir un vote pour quelque chose de différent. – recursive

0

Vous avez besoin d'une sélection combinée et opérateur, comparant à réponse acceptée, ce sera moins cher, car ne nécessitera pas des 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); 
      } 
     } 
1

Tout d'abord, votre code ne fait itérer sur la liste deux fois, il ne une fois itère.

Cela dit, votre Select est en train d'obtenir une séquence de tous les index; qui est plus facile à faire avec Enumerable.Range:

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

Comprendre pourquoi la liste ne sont pas en fait deux fois réitérée prendra un certain temps pour s'y habituer. Je vais essayer de donner une explication de base.

Vous devriez penser à la plupart des méthodes LINQ, telles que Select et Where en tant que pipeline. Chaque méthode fait un tout petit peu de travail.Dans le cas de Select, vous lui donnez une méthode, et il dit essentiellement: "Chaque fois que quelqu'un me demande mon prochain article, je vais d'abord demander ma séquence d'entrée pour un article, puis utiliser la méthode que j'ai pour le convertir en quelque chose d'autre. puis donnez cet objet à quiconque m'utilise. " Où, plus ou moins, dit, "chaque fois que quelqu'un me demande un article je vais demander ma séquence d'entrée pour un article, si la fonction dit que c'est bon, je vais le transmettre, sinon je vais continuer à demander des articles jusqu'à ce que j'en reçoive un qui passe. " Donc, quand vous les enchaînez, ce qui se passe, c'est que ToList demande le premier élément, il va à Où pour son premier élément, Où va à Sélectionner et lui demande son premier élément, Sélectionner va à la liste pour demander pour son premier article. La liste fournit ensuite son premier élément. Select transforme ensuite cet élément en ce qu'il doit cracher (dans ce cas, juste l'int 0) et le donne à Where. Où prend cet élément et exécute sa fonction qui détermine que c'est vrai et donc crache 0 à ToList, qui l'ajoute à la liste. Toute cette chose arrive ensuite plus de 9 fois. Cela signifie que Select finira par demander chaque élément de la liste une seule fois, et qu'il transmettra chacun de ses résultats directement à Where, ce qui alimentera 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 ne jamais répéter la séquence source qu'une fois (quand elles sont itérées une fois). Notez que, bien que cela vous semble compliqué au début, il est assez facile pour l'ordinateur de faire tout cela. Ce n'est pas aussi performant que cela puisse paraître au premier abord.

Questions connexes