2009-06-09 7 views
0

J'avais besoin d'une méthode pour me donner tout sauf le dernier élément d'une séquence. Ceci est ma mise en œuvre actuelle:C#: implémentation de SkipLast

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
    { 
     using (IEnumerator<T> iterator = source.GetEnumerator()) 
     { 
      if(iterator.MoveNext()) 
       while(true) 
       { 
        var current = iterator.Current; 
        if(!iterator.MoveNext()) 
         yield break; 
        yield return current; 
       } 
     } 
    } 

Ce dont j'ai besoin est de faire quelque chose avec tous les éléments sauf le dernier. Dans mon cas, j'ai une séquence d'objets avec diverses propriétés. Je les commande ensuite par date, et ensuite je dois faire un ajustement à tous, sauf l'article le plus récent (qui serait le dernier après la commande). Il est vrai que je ne suis pas encore dans ces enquêteurs et que je n'ai pas vraiment de questions ici: p Ce que je me demande, c'est si c'est une bonne implémentation, ou si j'ai fait une petite ou grosse gaffe quelque part. Ou si peut-être cette prise sur le problème est un étrange, etc

Je suppose qu'une implémentation plus générale aurait pu être une méthode AllExceptMaxBy. Puisque c'est un peu ce que c'est. Le MoreLinq a une méthode MaxBy et MinBy et ma méthode a besoin de faire la même chose, mais retourne tous les items sauf le maximum ou le minimum.

Répondre

8

Ceci est difficile, car "dernier élément" n'est pas un point d'arrêt de Markov: vous ne pouvez pas dire que vous avez atteint le dernier élément tant que vous n'avez pas essayé d'obtenir le suivant. C'est faisable, mais seulement si cela ne vous dérange pas d'être "un élément derrière". C'est en gros ce que fait votre implémentation actuelle, et ça a l'air correct, même si je l'écrirais probablement différemment.

Une autre approche serait d'utiliser foreach, ce qui donne toujours la valeur retournée précédemment, sauf si vous étiez à la première itération:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    T previous = default(T); 
    bool first = true; 
    foreach (T element in source) 
    { 
     if (!first) 
     { 
      yield return previous; 
     } 
     previous = element; 
     first = false; 
    } 
} 

Une autre option, plus proche de votre code:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    using (IEnumerator<T> iterator = source.GetEnumerator()) 
    { 
     if(!iterator.MoveNext()) 
     { 
      yield break; 
     } 
     T previous = iterator.Current; 
     while (iterator.MoveNext()) 
     { 
      yield return previous; 
      previous = iterator.Current; 
     } 
    } 
} 

Ce évite d'imbriquer tout aussi profondément (en effectuant une sortie anticipée si la séquence est vide) et utilise une condition "réelle" au lieu de while(true)

+0

Qu'est-ce qu'un point d'arrêt de Markov? Quoi qu'il en soit, de bonnes suggestions! La nidification profonde et tandis que (vrai) était quelques-unes des raisons pour lesquelles ma solution se sentait un peu bizarre, hehe. – Svish

+0

@Jon, merci de clarifier ce qu'est le vrai problème avec les fonctions Last-family. Dans mon cas, il semblerait que SkipLast (5) doive avoir 5 éléments derrière ... ce qui semble être un bon travail pour une file d'attente, peut-être. –

1

Votre implémentation me semble parfaite - c'est probablement comme ça que je le ferais. La seule simplification que je pourrais suggérer par rapport à votre situation est de commander la liste dans l'autre sens (c'est-à-dire ascendant plutôt que décroissant). Bien que cela ne convienne pas à votre code, il vous permet simplement d'utiliser collection.Skip(1) pour prendre tous les éléments sauf le plus récent.

Si cela n'est pas possible pour des raisons que vous n'avez pas indiquées dans votre message, votre implémentation actuelle ne pose aucun problème.

+0

C'est un bon point oui. En fait, je n'y ai pas pensé ... mais j'ai besoin de la séquence commandée par la suite. ce qui signifie que je devrais le commander et l'inverser à nouveau ou quelque chose. J'aurais pu travailler aussi je suppose ... mais ça fait un peu bizarre d'aller et venir comme ça: p – Svish

0

Si vous utilisez .NET 3.5, je suppose que vous pouvez utiliser:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    return source.TakeWhile((item, index) => index < source.Count() - 1)) 
} 
+0

Cela énumère cependant deux fois la collection (sauf si sourcis est une liste), donc c'est probablement une mauvaise idée! (Et ne fonctionnera même pas dans le cas général d'un itérateur non-déterministe.) – Noldorin

+1

Il faudrait également Count() au lieu de Count. Et en fait, il énumérerait la collection plusieurs fois, parce que l'expression lambda compterait les nœuds chaque fois qu'elle serait évaluée! –

+0

pour ma propre éducation: Serait-il correct si le compte a été fait une fois en dehors de la lambda? Je ne comprends pas pourquoi il énumérerait la collection deux fois. – Razzie

0
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    if (!source.Any()) 
    { 
     yield break; 
    } 
    Queue<T> items = new Queue<T>(); 
    items.Enqueue(source.First()); 
    foreach(T item in source.Skip(1)) 
    { 
     yield return items.Dequeue(); 
     items.Enqueue(item); 
    } 
} 
0

(ancienne réponse mis au rebut; Ce code a été testé et fonctionne.) Il imprime
premier
deuxième
PREMIER
DEUXIÈME
TROISIÈME


public static class ExtNum{ 
    public static IEnumerable skipLast(this IEnumerable source){ 
    if (! source.Any()) 
     yield break; 
    for (int i = 0 ; i <=source.Count()-2 ; i++) 
     yield return source.ElementAt(i); 
    yield break; 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
    Queue qq = new Queue(); 
    qq.Enqueue("first");qq.Enqueue("second");qq.Enqueue("third"); 
    List lq = new List(); 
    lq.Add("FIRST"); lq.Add("SECOND"); lq.Add("THIRD"); lq.Add("FOURTH"); 
    foreach(string s1 in qq.skipLast()) 
     Console.WriteLine(s1); 
    foreach (string s2 in lq.skipLast()) 
     Console.WriteLine(s2); 
    } 
} 
+0

Il n'y a aucun compte ou indexeur sur un IEnumerable. Donc, pour ce faire, vous devrez le mettre dans une liste ou quelque chose. Et cela signifie que vous devrez passer à travers les éléments deux fois. Une fois pour les mettre dans la liste, et une fois pour les céder. – Svish

Questions connexes