/// <summary> /// Analyse l'arbre issu de la première étape et le réduit /// </summary> /// <param name="LeNoeud"></param> private List <Noeud> AnalyserEtReduire(ref List <Noeud> Noeuds) { /* * J'ai testé plusieurs algorithmes, celui retenu est le plus rapide que j'ai trouvé 58 secondes contre 18 minutes pour le plus lent. * * J'utilise des List<T> et non des IEnumerable, car chaque collection est évaluée plusieurs fois, au final le cast en List est un gain de temps important. * * Pour ne pas prendre en modèle, un(des) noeud(s) équivalent(s) à un noeud déjà dans le dawg2etapes, la solution la plus efficace est de retirer ce(s) noeud(s) à la collection d'origine, le fait d'enlever des items au fur et à mesure rend chaque requête de plus en plus rapide. * Malgré cela l'exécution prend encore plus de 10 minutes si on travaille sur la liste "noeuds", qui contient au départ plus de 786 000 noeuds. * * Le fait de faire un groupBy sur la propriété "Rang" fait tomber le temps d'éxécution de façon considérable (+ de 10 minutes à 1 minute) * En effet deux noeuds de même rang sont quasiement équivalents. Ils ont la même profondeur, le même nombre d'enfants et ont le même état terminal. Pour l'équivalence, il reste à comparer les arcs sortants, la destination des arcs évoluant au fur et à mesure de la réduction, cette dernière comparaison ne peut pas être anticipée * Ce groupement crée 3742 collections dont la plus importante contient tous les noeuds terminaux sans enfants (254645 noeuds) qui de fait sont équivalents, donc une seule itération suffit à la réduire. */ AnnonceEtape("Début de la réduction de l'arbre en graphe."); int pourcent = 1; int progress = 0; int nbreNoeuds = Noeuds.Count; List <Noeud> dawg2etapes = new List <Noeud>(); List <IGrouping <string, Noeud> > parProfondeur = Noeuds.GroupBy(n => ((Noeud)n).Rang).OrderBy(x => x.Key).ToList(); foreach (IGrouping <string, Noeud> item in parProfondeur) { List <Noeud> lesNoeuds = item.ToList(); while (lesNoeuds.Count > 0) { Noeud model = (Noeud)lesNoeuds[0];//on prend en modèle le premier noeud (encore) disponible pour le rang en cours dawg2etapes.Add(FusionEquivalents(model, ref lesNoeuds, ref progress)); int x = progress * 100 / nbreNoeuds; if (pourcent < x) { pourcent = x; AnnonceProgression(pourcent); } } } //on réindexe les noeuds return(dawg2etapes.OrderBy(n => n.Numero).Select((n, i) => (Noeud)n.ReIndex(i + 1)).ToList()); }
/// <summary> /// Retourne le préfixe du nouveau mot, s'il existe déja dans le dictionnaire /// </summary> /// <param name="Mot">Mot à préfixer</param> /// <returns></returns> private List <Arc> ExtrairePrefixeCommun(string Mot, Noeud Noeud0) { List <Arc> prefixe = new List <Arc>(); Noeud enCours = Noeud0; for (int i = 0; i < Mot.Length; i++) { Arc a = enCours.Sortants.SingleOrDefault(aa => aa.Lettre == Mot[i] && aa.Destination.Entrants.Count == 1); //on recherche si le noeud en cours possède la lettre suivante comme arc sortant if (a == null) //si ça n'est pas le cas on sort du for. { break; } prefixe.Add(a); enCours = a.Destination; } return(prefixe); }
/// <summary> /// Retourne si le noeud est équivalent à celui en paramètre /// </summary> /// <param name="Autre"></param> /// <returns></returns> public bool IsEquivalent(Noeud Autre) { /* Pour être équivalents deux noeuds doivent: * -avoir la même profondeur (condition fixe 1) * -être tous les 2 terminaux ou pas (condition fixe 2) * -avoir le même nombre d'enfants (condition fixe 3) avec le groupement ces 3 conditions sont déjà vérifiée lors de la construction en 2 temps * -les arcs sortants doivent avoir les mêmes lettres allant vers les mêmes destinations.Dans la construction en 2 temps, les destinations étant redirigées au fur et à mesure de la reduction ctte condition sera évaluée au moment nécessaire */ if (this.Profondeur == Autre.Profondeur && this.IsTerminal == Autre.IsTerminal && this.NbreEnfants == Autre.NbreEnfants) { IEnumerable <string> destinationsThis = this.Sortants.Select(a => a.Serialize).Distinct().OrderBy(i => i); IEnumerable <string> destinationsAutre = Autre.Sortants.Select(a => a.Serialize).Distinct().OrderBy(i => i); return(destinationsThis.SequenceEqual(destinationsAutre)); } else { return(false); } }
/// <summary> /// Affecte la profodeur et le dernier enfant du nouveau suffixe et met à jour les noeuds "au-dessus" /// </summary> /// <param name="Final">Noeud Final du nouveau suffixe</param> private void MettreAjourProfondeur(Noeud Final, ref List <Noeud> Noeuds) { //A ce moment là le dictionnaire à encore la forme d'un arbre, donc chaque noeud ne possède qu'un arc entrant Noeud enCours = (Noeud)Final.Entrants[0].Origine; int i = 1; do { if (enCours.Profondeur < i)//si le noeud a une profondeur inférieure à la celle induite par le nouveau suffixe, on met à jour { enCours.Profondeur = i; } i++; enCours.NbreEnfants = enCours.Sortants.Count() + enCours.Sortants.Sum(a => a.Destination.NbreEnfants); enCours = enCours != Noeuds[0] ? (Noeud)enCours.Entrants[0].Origine : null; } while (enCours != null); }
/// <summary> /// Crée les arcs et les noeuds nécessaires à joindre le dernier noeud du préfixe commun au premier noeud du suffixe commun /// </summary> /// <param name="Prefixe">Dernier noeud du préfixe commum</param> /// <param name="Suffixe">Premier noeud du suffixe commun</param> /// <param name="ACreer">Lettres restant à créer</param> private void JoindreLesChemins(Noeud Prefixe, Noeud Suffixe, char[] ACreer) { Noeud enCours = Prefixe; for (int i = 0; i < ACreer.Length; i++) { char lettre = ACreer[i]; Noeud destination; if (i == ACreer.Length - 1) { destination = Suffixe; } else { destination = new Noeud(NombreNoeuds++); dawg.Add(destination); } enCours.Sortants.Add(new Arc(lettre, enCours, destination)); enCours = destination; } }
/// <summary> /// Thread de la construction en 2 étapes /// </summary> private void leThread2Etapes() { List <Noeud> noeuds = ConstruireArbre(); dawg = AnalyserEtReduire(ref noeuds); //Affectation du résultat DAWG = dawg[0]; AnnonceEtape("Ecriture du fichier."); Noeud.Serialize(dawg, Mots.Count, NomDicoDawgODS7); //chrono.Stop(); //==================Ici la construction est finie //Cette étape n'est utile que pour la démo et le débug ComparerListeMotsEtDAWG(); TravailEnCours = DawgResolver.TravailEnCours.Aucun; }
/// <summary> /// Retourne cet arc aprés avoir changé l'origine /// </summary> /// <param name="N">Nouvelle origine</param> /// <returns>Le même arc, pour pouvoir être utilisé dans une requête Linq</returns> public Arc SetOrigine(Noeud N) { Origine = N; return(this); }
/// <summary> /// Retourne cet arc aprés avoir changé la destination /// </summary> /// <param name="N">Nouvelle destination</param> /// <returns>Le même arc, pour pouvoir être utilisé dans une requête Linq</returns> public Arc SetDestination(Noeud N) { Destination = N; return(this); }
/// <summary> /// Ajoute un mot au dictionnaire /// </summary> /// <param name="mot">Mot à ajouté</param> /// <remarks>On vérifie d'abord que le mot n'est pas déjà présent</remarks> public void AjouterUnMot(string mot) { //chrono.Restart(); TravailEnCours = DawgResolver.TravailEnCours.AjoutMot; mot = mot.ToUpper();//au cas ou il soit en minuscule if (!MotAdmis(mot)) { Noeud aRejoinde = DAWG; string suffixe = mot; List <Arc> prefixe = ExtrairePrefixeCommun(mot, DAWG); int longueur = prefixe.Count; if (longueur > 0)//si un suffixe existe, on continue à partir de là { AnnonceEtape(string.Format("Préfixe trouvé \"{0}\".", Arc.RetourneMot(prefixe))); aRejoinde = prefixe.Last().Destination; suffixe = mot.Substring(longueur); } else { AnnonceEtape("Pas de préfixe trouvé");//théoriquement ça n'est pas possible, en effet, il n'existe aucune lettre qui ne commence aucun mot dans le dictionnaire ASCII } List <Arc> arcsSuffixe = new List <Arc>(); //On part du noeud terminal sans enfants et on remonte le graphe pour trouver le suffixe commun Noeud enCours = dawg.Single(n => n.Sortants.Count == 0 && n.IsTerminal); int i; for (i = suffixe.Length - 1; i > -1; i--) { char lettre = suffixe[i]; Arc arc = enCours.Entrants.SingleOrDefault(a => a.Lettre == lettre && !a.Origine.IsTerminal && a.Origine.Sortants.Count == 1); if (arc == null)//il n'y a plus de chemin possible { break; } arcsSuffixe.Insert(0, arc); enCours = arc.Origine; } AnnonceEtape(string.Format("Suffixe trouvé \"{0}\".", Arc.RetourneMot(arcsSuffixe))); JoindreLesChemins(aRejoinde, enCours, suffixe.Take(i + 1).ToArray()); Mots.Add(mot); AnnonceEtape("Ecriture du nouveau fichier DAWG."); Noeud.Serialize(dawg, Mots.Count, "DawgEn2Temps.txt"); AnnonceEtape("Mot ajouté et fichier enregistré."); } else { AnnonceEtape(string.Format("Le mot \"{0}\" existe déja.", mot)); } //chrono.Stop(); //==================Ici l'ajout du mot est fini //Cette étape n'est utile que pour la démo et le débug ComparerListeMotsEtDAWG(); TravailEnCours = DawgResolver.TravailEnCours.Aucun; }
/// <summary> /// Charge le DAWG compressé dans un fichier. /// Ensuite compare la liste de mots avec celle du fichier ASCII /// </summary> /// <param name="FileName">Adresse du fichier compressé</param> /// <returns>DAWG</returns> public void ChargerFichierDAWG(string nomdico) { TravailEnCours = DawgResolver.TravailEnCours.ChargementFichierDAWG; //chrono.Restart(); AnnonceEtape("Début du chargement du dictionnaire DAWG."); var assembly = Assembly.GetExecutingAssembly(); string resourceName = assembly.GetManifestResourceNames().Single(str => str.EndsWith(nomdico)); using (Stream stream = assembly.GetManifestResourceStream(resourceName)) using (StreamReader reader = new StreamReader(stream, true)) { string content = reader.ReadToEnd(); string[] lignes = content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); //string[] lignes = File.ReadAllLines(FileName); //le nombre de mots est présent dans le fichier par soucis de compatibilité avec le tutoriel de CArlVB, mais il n'est pas utile NombreNoeuds = lignes.Count() - 2; // Convert.ToInt32(lignes[1].Split(':')[1]); /*On connait à l'avance le nombre de noeuds car c'est écrit en entête par soucis de compatibilité avec le tutoriel de CArlVB * Cependant, on aurait pu le déduire à partir de lignes.Length * Utiliser un tableau permet de regarder au bon index si le noeud a déjà été créé par un arc entrant ou s'il faut le faire */ Legacy = new int[28, NombreNoeuds + 1]; Noeud[] noeuds = new Noeud[NombreNoeuds]; for (int i = 0; i < NombreNoeuds; i++) { if (noeuds[i] == null) //on vérifie si le noeud à déserialiser n'a pas déjà été créé par un arc { noeuds[i] = new Noeud(i + 1); //on l'initialise si nécessaire } Noeud n = noeuds[i]; string ligne = lignes[i + 2];//on lit la ligne correspondante, il faut penser à sauter les 2 lignes d'entête n.IsTerminal = ligne.EndsWith("#"); Legacy[27, n.Numero] = n.IsTerminal ? 1 : 0; if (ligne != "#")//on exclu le cas particulier du noeud terminal sans enfant { //on désérialize les arcs sortants string[] arcs = ligne.Replace("#", "").Split("-".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); n.Sortants = (from a in arcs select new Arc(n, a, noeuds) ).ToList(); foreach (var s in n.Sortants) { Legacy[(int)s.Lettre - AscShift, s.Origine.Numero] = s.Destination.Numero; } } } DAWG = noeuds[0]; dawg = noeuds.ToList(); AnnonceEtape("Fin du chargement du dictionnaire DAWG."); //chrono.Stop(); //==================Ici la lecture du fichier est finie //Cette étape n'est utile que pour la démo et le débug //ComparerListeMotsEtDAWG(); TravailEnCours = DawgResolver.TravailEnCours.Aucun; //return dawg[0]; } }