2010-04-24 9 views
9

J'essaie d'implémenter le quicksort dans un style fonctionnel en utilisant C# en utilisant linq, et ce code fonctionne aléatoirement/ne fonctionne pas, et je ne peux pas comprendre pourquoi.
Important à mentionner: Lorsque je l'appelle sur un tableau ou une liste, cela fonctionne très bien. Mais sur un inconnu-ce-it-vraiment-est IEnumerable, il devient fou (perd des valeurs ou des accidents, en général fonctionne parfois..)
Le code:Le quicksort fonctionnel C# échoue

public static IEnumerable<T> Quicksort<T>(this IEnumerable<T> source) 
     where T : IComparable<T> 
    { 
     if (!source.Any()) 
      yield break; 
     var pivot = source.First(); 
     var sortedQuery = source.Skip(1).Where(a => a.CompareTo(source.First()) <= 0).Quicksort() 
          .Concat(new[] { pivot }) 
          .Concat(source.Skip(1).Where(a => a.CompareTo(source.First()) > 0).Quicksort()); 
     foreach (T key in sortedQuery) 
      yield return key; 
    } 

Pouvez-vous trouver des défauts ici cela ferait échouer cela?

Edit: Certains meilleur code de test:

 var rand = new Random(); 
     var ienum = Enumerable.Range(1, 100).Select(a => rand.Next()); 
     var array = ienum.ToArray(); 
     try 
     { 
      array.Quicksort().Count(); 
      Console.WriteLine("Array went fine."); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("Array did not go fine ({0}).", ex.Message); 
     } 
     try 
     { 
      ienum.Quicksort().Count(); 
      Console.WriteLine("IEnumerable went fine."); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("IEnumerable did not go fine ({0}).", ex.Message); 
     } 
+1

Que voulez-vous dire 'unknown-what-it-really-is IEnumerable' ?? C'est une méthode générique, donc les types de vos objets sont toujours connus. –

+0

Je veux dire que je ne sais pas ce qui se trouve sous le shell IEnumerable. Est-ce une liste? un tableau? Ce que j'ai essayé et ce qui a échoué était d'une liste où j'ai fait fondamentalement "Random rand = ...; int [100] .Select (a => rand.Next());" – Rubys

Répondre

7

Quelques cas dénombrables, tels que ceux qui sont renvoyés par les requêtes LINQ to SQL ou Entity Framework, ne sont conçus pour être itéré fois. Votre code nécessite plusieurs itérations et se bloque ou se comporte étrangement sur ces types d'instances. Vous devez matérialiser ces énumération avec ToArray() ou une méthode similaire en premier.

Vous devriez également réutiliser ce pivot de sorte que vous n'ayez pas à conserver l'itération pour les éléments premiers et restants. Cela peut ne pas résoudre complètement le problème, mais ça va aider dans certains cas: (. Vous ne avez pas besoin de itérer par la sortedQuery - il suffit de retourner, il est déjà un IEnumerable<T>)

public static IEnumerable<T> Quicksort<T>(this IEnumerable<T> source) 
    where T : IComparable<T> 
{ 
    if (!source.Any()) 
     return source; 
    var pivot = source.First(); 
    var remaining = source.Skip(1); 
    return remaining 
     .Where(a => a.CompareTo(pivot) <= 0).Quicksort() 
     .Concat(new[] { pivot }) 
     .Concat(remaining.Where(a => a.CompareTo(pivot) > 0).Quicksort()); 
} 

Sur une note connexe, pourquoi ressentez-vous le besoin de ré-implémenter cette fonctionnalité? Enumerable.OrderBy le fait déjà pour vous.


Réponse à jour:

vos tests échouent parce que votre test est erroné, pas l'algorithme.

Random est une source d'entrée non déterministe et, comme je l'ai expliqué ci-dessus, la méthode de tri doit effectuer plusieurs itérations sur la même séquence. Si la séquence est totalement aléatoire, elle aura des valeurs différentes sur les itérations suivantes. Essentiellement, vous essayez de faire une séquence rapide dont les éléments ne cessent de changer!

Si vous souhaitez que le test réussisse, vous devez rendre l'entrée cohérente. Utilisez une graines pour le générateur de nombres aléatoires:

static IEnumerable<int> GetRandomInput(int seed, int length) 
{ 
    Random rand = new Random(seed); 
    for (int i = 0; i < length; i++) 
    { 
     yield return rand.Next(); 
    } 
} 

Puis:

static void Main(string[] args) 
{ 
    var sequence = GetRandomInput(248917, 100); 
    int lastNum = 0; 
    bool isSorted = true; 
    foreach (int num in sequence.Quicksort()) 
    { 
     if (num < lastNum) 
     { 
      isSorted = false; 
      break; 
     } 
     lastNum = num; 
    } 
    Console.WriteLine(isSorted ? "Sorted" : "Not sorted"); 
    Console.ReadLine(); 
} 

Il reviendra triés.

+0

Mon énumérable était réellement juste Enumerable.Range, et il a échoué toujours. En outre, j'ai simplement renvoyé le sortedQuery, mais j'obtiens une erreur - "Impossible de renvoyer une valeur à un itérateur.Utilisez l'instruction return return pour retourner une valeur, ou yield break pour terminer l'itération." Et - Et - Je n'ai pas besoin de mettre en œuvre cela, je veux juste, en essayant d'apprendre la programmation fonctionnelle. – Rubys

+0

@Rubys: Vous avez raison à propos de l'erreur "Impossible de renvoyer une valeur" - Je viens de corriger cela, le problème était que la "rupture de rendement" au début était mélangée avec un retour direct à la fin. Je vais essayer ceci avec 'Enumerable.Range' et voir ce qui se passe. – Aaronaught

+0

@Rubys: Fonctionne très bien sur 'Enumerable.Range' ici. Postez votre code de test qui échoue. – Aaronaught