2010-08-20 4 views
0

J'ai un List<> d'objets contenant deux chaînes et un DateTime. Je veux créer une autre liste des mêmes objets contenant uniquement les derniers éléments uniques en utilisant les deux chaînes en tant que clés et la dernière valeur DateTime. En SQL, pensez ce qui suit:Boucle récursive sur List <> provoquant stackoverflow

SELECT col1, col2, MAX(datetime) FROM table GROUP BY col1, col2 

Ceci donne la liste unique de col1, col2 et le dernier datetime. Donc .. J'essaye de le faire en code avec deux listes. Un avec des doublons dedans qui analysent et attrapent seulement les derniers éléments uniques pour peupler une deuxième liste. Les ensembles de données que j'ai sont énormes, donc en passant par la liste des doublons puis en vérifiant si l'élément est dans la liste unique, s'il ne l'ajoute pas, si c'est le cas, comparer les dates etc. J'ai donc pensé pouvoir parcourir de manière récursive la liste des doublons et récupérer les objets uniques trouver leur max datetime et supprimer les non max au fur et à mesure de la boucle, rendant ma liste dupliquée de plus en plus petite, accélérant ainsi les choses. (J'espère que vous me suivez toujours ..)

Donc de toute façon. J'ai écrit une boucle récursive avec deux listes, mais quand je boucle en boucle, j'obtiens un System.StackOverflowException à propos de la 3000ème itération.

Voici mon code. Imaginez le ListWithDuplicates est plein de données. Le ListDataItem a plus de propriétés que j'ai omis. Mais ma question principale est pourquoi est-ce que je ne peux pas boucler le public list de cette manière sans causer le StackOverflowException?

using System; 
using System.Net; 
using System.IO; 
using System.Collections.Generic; 
using System.Linq; 

public class RecursionTest 
{ 
    public List<listDataItem> ListWithDuplicates { get; set; } 
    public List<listDataItem> ListWithUniques { get; set; } 

    public RecursionTest() 
    { 
     Process(); 
    } 

    public void Process() 
    { 
     int rowcount = 0; 
     int duplicates = 0; 
     int total = 0; 
     RecursiveLoopForUnique(ref rowcount, ref duplicates, ref total, "", ""); 
    } 

    private void RecursiveLoopForUnique(ref int rowcount, ref int duplicates, ref int total, string col1, string col2) 
    { 
     if (rowcount > 0) 
      duplicates += ListWithDuplicates.RemoveAll(z => z.COL1 == col1 && z.COL2 == col2); 
     if (ListWithDuplicates.Count > 0) 
     { 
      foreach (listDataItem item in ListWithDuplicates) 
      { 
       rowcount++; 
       if (ListWithUniques.FindAll(z => z.COL1 == item.COL1 && z.COL2 == item.COL2).Count < 1) 
       { 
        ListWithUniques.Add(ListWithDuplicates.FindAll(z => z.COL1 == item.COL1 && z.COL2 == item.COL2).OrderByDescending(z => z.DATETIME).First()); 
        col1 = item.COL1; 
        col2 = item.COL2; 
        break; 
       } 
      } 
      RecursiveLoopForUnique(ref rowcount, ref duplicates, ref total, col1, col2); 
     } 
     else 
      return; 
    } 

    public class listDataItem 
    { 
     public string COL1 { get; set; } 
     public string COL2 { get; set; } 
     public DateTime DATETIME { get; set; }    

     public listDataItem(string col1, string col2, DateTime datetime) 
     { 
      COL1 = col1; 
      COL2 = col2; 
      DATETIME = datetime; 
     } 
    } 
} 
+0

Serait-il possible de le réécrire sans le 'break;' et vide 'return;'? Cela ressemble à demander des ennuis. – FrustratedWithFormsDesigner

+0

@FrustratedWithFormsDesigner - vous avez raison. bon point. J'ai ajouté cette instruction if if car j'obtenais l'exception de débordement. Je n'ai pas non plus besoin de passer les valeurs col dans la fonction récursive et je pourrais les avoir supprimées après avoir quitté foreach. J'ai joué avec la fonction en essayant de venir à l'exception. aucun problème du tout ;-) – craigpj

Répondre

2

Que diriez-vous ceci:

Dictionary<string, item> destDict = new Dictionary<string, item>(); 

foreach (item curr in items) 
{ 
    string key = curr.col1 + curr.col2; 
    if (!destDict.Keys.Contains(key)) 
    { 
     destDict.Add(key, curr); 
    } 
    else 
    { 
     if (destDict[key].date < curr.date) 
     { 
      destDict[key].date = curr.date; 
     } 
    } 
} 

Je l'ai testé ce sur une liste contenant 1000 chacun des 2 paires uniques col1/col2. Fonctionne bien et était plus rapide qu'un groupe LINQby/select.

+0

Merci à tous ceux qui ont aidé sur ce coup. Cette méthode fournie par @t_scho a bien fonctionné et elle est incroyablement rapide. Je l'ai modifié légèrement en utilisant un DateTime.CompareTo et assigné l'objet curr au dictionnaire lorsque le DATETIME était postérieur à l'élément en cours dans la liste du Dictionnaire. – craigpj

+0

Dictionnaire destDict = nouveau Dictionary (); Foreach (listDataItem curr dans ListWithDuplicates) { string key = curr.COL1 + curr.COL2; if (! DestDict.Keys.Contains (key)) destDict.Add (clé, curr); sinon { if (curr.DATETIME.CompareTo (destDict [clé] .DATETIME)> 0) { destDict [clé] = curr; } sinon en double ++; } rowcount ++; } – craigpj

2

LINQ, yay.

listDataItem latestListDataItem = 
    ListWithDuplicates.Where(item => item.COL1 == yourCol1Param && item.COL2 == yourCol2Param) 
         .Max(item => item.DATETIME); 

MSDN note sur ..

Où: http://msdn.microsoft.com/en-us/library/bb534803.aspx

Max: http://msdn.microsoft.com/en-us/library/bb347632.aspx

OrderBy: http://msdn.microsoft.com/en-us/library/bb534966.aspx

Dernière: http://msdn.microsoft.com/en-us/library/bb358775.aspx

+1

Vous n'avez pas besoin d'utiliser OrderBy (...). Last(), vous pouvez utiliser Max (item => item.DateTime) à la place. – Juliet

+1

Je dois admettre, je ne comprends pas du tout comment cela résout son problème. Qu'est-ce qui vous fait penser qu'il cherche la date sur une combinaison particulière de C1 et C2, plutôt que tous? – mquander

+0

@mquander: Je suppose que c'est ce que l'affiche veut d'une des choses qu'il a dit dans son post. Il a dit "ne contenant que les derniers éléments uniques en utilisant les deux chaînes comme clés", si vous utilisez les deux chaînes comme clés, vous obtenez un groupe d'éléments, qui sont uniques par leur date, puis vous filtrez la dernière date et vous avez seulement un article .. –

0

Je ne suis pas su Re sur la syntaxe, mais il devrait être proche.

from d in DupsList 
group d.DATETIME on d.col1, d.col2 in grp 
select new listDataItem (grp.Key.col1, grp.Key.col2, grp.Max()}; 
0

Eh bien, si vous avez plus de quelques milliers de paires uniques de C1, C2, alors vous allez rencontrer, puisque vous récursion une fois pour chaque groupe unique.

Il y a plusieurs façons de résoudre ce problème; celui qui serait plus clair et plus rapide serait de trier la liste par C1 et C2, puis de la descendre exactement une fois pour trouver la date la plus récente dans chaque groupe.Si vous n'êtes pas marié à réimplémentant vous-même, la meilleure façon est la suivante:

ListWithUniques = ListWithDuplicates 
    .GroupBy(x => new { COL1, COL2 }) 
    .Select(g => g.OrderByDescending(x => x.DATETIME).First()) 
0
SELECT col1, col2, MAX(datetime) FROM table GROUP BY col1, col2 

dans LINQ:

var query = from row in table 
      group row into g 
      select new 
      { 
       Col1 = g.Key.Col1, 
       Col2 = g.Key.Col2, 
       Date = g.Max(b => b.Date) 
      }; 

Et sous une forme potentiellement plus utile:

var dict = query.ToDictionary(a => new { a.Col1, a.Col2 }, a => a.Date); 

Ensuite, vous pouvez faire référence comme si:

DateTime specificMaxDate = dict[new { Col1 = 2, Col2 = 3 }]; 
+0

remercie Ian pour votre temps et vos suggestions à ce sujet. Très appréciée. – craigpj