Oui ça marche
Dim leTexte As String = File.ReadAllText("PBtxt.Txt") Dim trim = Regex.Replace(leTexte, "\x00\s+", "") File.WriteAllText("Sortie.txt", trim)
PS c'est p'tet un détail, mais y'a pas de retour à la ligne à la fin
Tout d'abord, le caractère c'est \u0020, et pas u0020\.
\u, c'est pour Unicode.
Quand j'ouvre ton fichier avec notepad++, en affichant les caractères invisibles, on voit un NUL avant les espaces (points oranges)
Donc le problème réside là.
A priori, NUL, c'est \00 (ou \u0000), mais il faut vérifier.
Je l'ouvre avec XVI32 (aussi moche qu'efficace !) et après DC-13, j'ai bien 00.
Et pareil pour les autres.
Tu peux donc remplacer tous les caractères \0 par rien, puis faire ton Trim.
Je vais essayer si on peut le faire d'un coup avec une regex.
Merci beaucoup encore pour ton aide Whismeril. Ton premier commentaire m'a donné la piste pour corriger le code.
J'ai remplacé
Select Array.ConvertAll(row.Cells.Cast(Of DataGridViewCell).ToArray, Function(c) If(c.Value IsNot Nothing, c.Value.ToString.Trim(), ""))
Par
Select Array.ConvertAll(row.Cells.Cast(Of DataGridViewCell).ToArray, Function(c) If(c.Value IsNot Nothing, c.Value.ToString.Replace(vbNullChar, "").Trim(), ""))
Et ça fonctionne !
Certes.
Mais typiquement c'est une mauvaise utilisation du datagridview.
La manipulation des données devrait être faite dans la source de données, ton datatable ici.
Et ensuite juste réactualiser l'affichage.
Tous les cast que tu es obligé de faire sont très gourmands en ressources et en temps
Vous n’avez pas trouvé la réponse que vous recherchez ?
Posez votre questionJe me suis amusé à te montrer le gain de temps sans utiliser de datatable et avec le datagridview seulement en affichage (le moins de Cast possible)
D'abord, j'ai créé un fichier "origine" de 500k lignes et un fichier de mise à jour de 33k lignes" avec cette méthode
''' <summary> ''' Génére un fichier d'entrée, si le pas est 1, le prix varie de 0 à 80, ''' Si le pas est différent, alors le prix est toujours 99.99 ce qui servira de valeur de mise à jour ''' </summary> ''' <param name="Filename"></param> ''' <param name="NbLignes"></param> ''' <param name="Pas"></param> Private Sub GenereFichier(Filename As String, NbLignes As Integer, Pas As Integer) Dim liste As New List(Of String) Dim rnd As New Random() Dim null As String = Convert.ToChar(0).ToString() liste.Add("Part,Value") For i = 1 To NbLignes Step Pas liste.Add($"{$"toto{i}{null}",-15},{If(Pas = 1, $"{rnd.Next(0, 79)}.{rnd.Next(0, 99)}", "99.99")}") Next File.WriteAllLines(Filename, liste) End Sub
Y'a même le caractère null et le padding de la référence.
Que j'ai appelée 2 fois comme ça
GenereFichier("FichierOrigine.csv", 500000, 1) GenereFichier("MiseAJour.csv", 500000, 15)
Une fois fait, j'ai écrit un code qui
''' <summary> ''' Test avec un dico et traitement du fichier entier avec la regex ''' </summary> ''' <param name="Filename"></param> ''' <returns></returns> Private Function CreerDico(Filename As String) As Dictionary(Of String, Double) Dim fichier As String = File.ReadAllText(Filename) Dim trim = Regex.Replace(fichier, "\x00\s+", "") Dim res As New Dictionary(Of String, Double) Dim lignes As String() = trim.Split(vbCrLf, StringSplitOptions.RemoveEmptyEntries) For Each l As String In lignes.Skip(1) Dim datas As String() = l.Split(",") res.Add(datas(0), Convert.ToDouble(datas(1), CultureInfo.InvariantCulture)) Next Return res End Function ''' <summary> ''' Charge le second fichier et met à jour le dictionnaire ''' </summary> ''' <param name="Filename"></param> ''' <param name="Dico"></param> Private Sub MajDico(Filename As String, ByRef Dico As Dictionary(Of String, Double)) Dim lignes As String() = File.ReadAllLines(Filename) Dim maRegex As New Regex("\x00\s+") For Each l As String In lignes.Skip(1) Dim datas As String() = l.Split(",") Dim key As String = maRegex.Replace(datas(0), "") Dico(key) = Convert.ToDouble(datas(1), CultureInfo.InvariantCulture) Next End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim depart As DateTime = DateTime.Now Dim intermediaire As DateTime Dim fin As DateTime 'lire le fichier d'origine sous la forme d'un dictionnaire Dim dico As Dictionary(Of String, Double) = CreerDico("FichierOrigine.csv") 'Faire la mise à jour du dictionnaire avec le second fichier MajDico("MiseAJour.csv", dico) 'convertir en liste de DatasCryptic pour le binding Dim liste As List(Of DatasCryptic) = dico.Select(Function(x) New DatasCryptic With { .Part = x.Key, .Value = x.Value }).ToList() 'Export du fichier mis à jour File.WriteAllText("Fichier de sortie.txt", $"Part,Value{vbCrLf}{String.Join(vbCrLf, liste.Select(Function(d) $"{d.Part},{d.Value}"))}") intermediaire = DateTime.Now 'binding DataGridView1.DataSource = liste fin = DateTime.Now TextBox1.Text = $"Durée : {(fin - depart)}{vbCrLf}Dont affichage : {fin - intermediaire}" End Sub
Et la classe
Public Class DatasCryptic Public Property Part As String Public Property Value As Double End Class
Et ça donne ça compilé en debug (même pas en release), et sans avoir cherché à particulièrement optimiser.
1 seconde après le clic sur le bouton.
J'ai un Ryzen 7, 1.8Ghz et 16 Go de RAM, c'est pas mal, mais pas un super calculateur non plus.
Je pense donc que chez toi aussi le gain serait de cet ordre.
Bonjour, je continue à m'amuser.
La conversion du dico en liste est forcément un peu chronophage.
Mais d'un autre coté, accéder directement à la bonne valeur grâce à la clé fait gagner un temps considérable par rapport à une recherche.
J'ai donc essayé, dans un premier temps avec un liste triée.
''' <summary> ''' Test avec une liste et traitement du fichier entier avec la regex ''' </summary> ''' <param name="Filename"></param> ''' <returns></returns> Private Function CreerListe(Filename As String) As List(Of DatasCryptic) Dim fichier As String = File.ReadAllText(Filename) Dim trim = Regex.Replace(fichier, "\x00\s+", "") Dim res As New List(Of DatasCryptic) Dim lignes As String() = trim.Split(vbCrLf, StringSplitOptions.RemoveEmptyEntries) For Each l As String In lignes.Skip(1) Dim datas As String() = l.Split(",") res.Add(New DatasCryptic With { .Part = datas(0), .Value = Convert.ToDouble(datas(1), CultureInfo.InvariantCulture) }) Next Return res End Function ''' <summary> ''' Charge le second fichier et met à jour la Liste, la liste doit être triée ''' </summary> ''' <param name="Filename"></param> ''' <param name="Liste"></param> Private Sub MajListeTriee(Filename As String, ByRef Liste As List(Of DatasCryptic)) Dim maj As List(Of DatasCryptic) = CreerListe(Filename) Dim indexMaj As Integer = 0 Dim m As DatasCryptic = maj(indexMaj) For i As Integer = 0 To Liste.Count - 1 If Liste(i).Part = m.Part Then 'on echange les datas dans la liste Liste(i) = m indexMaj += 1 If indexMaj = maj.Count Then 'c'est fini, pas la peine de continuer à boucler sur Liste Exit For Else 'on prend la valeur à mettre à jour suivante m = maj(indexMaj) End If End If Next End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click Dim depart As DateTime = DateTime.Now Dim intermediaire As DateTime Dim fin As DateTime 'lire le fichier d'origine sous la forme d'un dictionnaire Dim liste As List(Of DatasCryptic) = CreerListe("FichierOrigine.csv") 'Faire la mise à jour du dictionnaire avec le second fichier MajListeTriee("MiseAJour.csv", liste) 'Export du fichier mis à jour File.WriteAllText("Fichier de sortie.txt", $"Part,Value{vbCrLf}{String.Join(vbCrLf, liste.Select(Function(d) $"{d.Part},{d.Value}"))}") intermediaire = DateTime.Now 'binding DataGridView1.DataSource = liste fin = DateTime.Now TextBox1.Text = $"Durée : {(fin - depart)}{vbCrLf}Dont affichage : {fin - intermediaire}" End Sub End Class
Là encore, j'ai plus ou moins une seconde.
Donc le "temps perdu" dans le for est plus ou moins compensé par le fait qu'on ne convertit plus un dico en listes.
Mais ce code ne marchera pas si la liste n'est pas triée, alors que le dico si. Et aucun des 2 ne sait gérer une nouvelle valeur dans le fichier de mise à jour.
Et aucun des 2 ne sait gérer une nouvelle valeur dans le fichier de mise à jour.
Ha autant pour moi, le dico ajoute une valeur s'il tombe sur une clé inconnue (alors peut-être une différence avec le framework 6 ici utilisé et le 4.8 dont j'ai plus l'habitude ou erreur de ma part...).
Donc le dico sait gérer les nouvelles valeurs, mais ne les trie pas, elles sont ajoutées à la fin
Pour une liste non triée (et donc des fichiers non triés).
J'ai d'abord dû générer 2 fichiers en bazar.
J'ai donc modifié ma méthode de création.
Elle utilise une méthode de mélange "magique" que j'ai péché il y a longtemps sur le net (et donc je ne sais plus où) en triant par une valeur aléatoire. Mais random n'est pas assez aléatoire, alors le gars avait eu l'idée de génie de générer un GUID.
Par contre, à la fin, pour que ce soit lisible et vérifiable, il faut pouvoir trier la collection.
Or, dans l'ordre de tri en string "toto2" arrive après "toto10", j'ai donc aussi modifié pour que "toto2" devienne "toto02"
''' <summary> ''' Génére un fichier d'entrée, si le pas est 1, le prix varie de 0 à 80, ''' Si le pas est différent, alors le prix est toujours 99.99 ce qui servira de valeur de mise à jour ''' </summary> ''' <param name="Filename"></param> ''' <param name="NbLignes"></param> ''' <param name="Pas"></param> Private Sub GenereFichier(Filename As String, NbLignes As Integer, Pas As Integer) Dim liste As New List(Of String) Dim entete As New List(Of String) From {"Part,Value"} Dim rnd As New Random() Dim null As String = Convert.ToChar(0).ToString() Dim paddNumero As New String("0"c, NbLignes.ToString().Length) '1 deviendra 001 si NbLignes est un nombre à 3 chiffres For i = 1 To NbLignes Step Pas liste.Add($"{$"toto{i.ToString(paddNumero)}{null}",-15},{If(Pas = 1, $"{rnd.Next(0, 79)}.{rnd.Next(0, 99)}", "99.99")}") Next 'trie avec un aléa encore plus aléatoire que random Dim melange = liste.OrderBy(Function(x) Guid.NewGuid()) File.WriteAllLines(Filename, entete.Concat(melange)) End Sub
Et ça m'a fait 2 fichiers encore plus beaux que les précédents ;).
Pour l'instant, je n'ai pas ajouté de valeurs inconnues, donc en première intention et vu que mes tests précédents m'ont prouvé que la méthode la plus rapide c'est avec les listes triées, ben j'ai juste trié les listes en plus du traitement précédent.
''' <summary> ''' Charge le second fichier, tri les listes et met à jour la Liste ''' </summary> ''' <param name="Filename"></param> ''' <param name="Liste"></param> Private Sub MajListePasTriee(Filename As String, ByRef Liste As List(Of DatasCryptic)) Dim maj As List(Of DatasCryptic) = CreerListe(Filename).OrderBy(Function(x) x.Part).ToList() Dim indexMaj As Integer = 0 Dim m As DatasCryptic = maj(indexMaj) Liste = Liste.OrderBy(Function(x) x.Part).ToList() For i As Integer = 0 To Liste.Count - 1 If Liste(i).Part = m.Part Then 'on echange les datas dans la liste Liste(i) = m indexMaj += 1 If indexMaj = maj.Count Then 'c'est fini, pas la peine de continuer à boucler sur Liste Exit For Else 'on prend la valeur à mettre à jour suivante m = maj(indexMaj) End If End If Next End Sub
Entre 2 et 3 secondes. Ce qui n'est pas mal du tout.
En effet, les fonctions de tri sont assez longues, car elles itèrent de nombreuses fois la collection pour tout ranger dans l'ordre.
Mais si j'ai une nouvelle valeur dans le fichier de mise à jour, c'est mort, la boucle va aller jusqu'à la fin de la liste principale et sortir.
Pas d'ajout de la nouvelle valeur (ce qui n'est pas bien) mais surtout pas de correction des données située après cette nouvelle valeur dans la liste de mise à jour triée => régression.
Je testerai un truc à base while et d'inférieur plus tard dans la journée, j'ai à faire maintenant.
Et enfin
Cette méthode trie, ajoute ce qui va avant la liste principale, met à jour ce qui existe, insère ce qu'il manque au milieu, au bon endroit et ajoute tout ce qui va après.
J'ai du créé un fichier d'origine dans lequel il manque aléatoirement de valeurs en cours (au final 6364 lignes).
Et un fichier de mise à jour avec ces 6364 valeurs, 1761 valeurs avant (aléatoire), 3151 (choisis un peu au pif) valeurs après, et 25391 valeurs à mettre à jour.
Au début, je crée une variable log, si elle vaut true, ça écrit plusieurs fichiers permettant de faire de vérification (telle valeur est censée être avant, telle autre insérée au milieu, etc...)
Mais c'est 3 à 4 fois plus long.
Pour qu'elle fonctionne, il faudra ajouter ces 3 lignes dans la classe DatasCryptic
Public Overrides Function ToString() As String Return $"{Part} : {Value}" End Function
''' <summary> ''' Charge le second fichier, tri les listes et met à jour la Liste ''' Il peut y avoir des nouvelles valeurs ''' </summary> ''' <param name="Filename"></param> ''' <param name="Liste"></param> Private Sub MajListePasTrieeEtNouvellesValeurs(Filename As String, ByRef Liste As List(Of DatasCryptic)) Dim log As Boolean = False 'si true, loggue les valeurs avant, insérées, mises à jour et après dans des fichiers dédiés, tu pourras si tu le veux faire des vérifications, mais c'est beaucoup plus long Dim maj As List(Of DatasCryptic) = CreerListe(Filename).OrderBy(Function(x) x.Part).ToList() Dim indexMaj As Integer = 0 Dim m As DatasCryptic = maj(indexMaj) Liste = Liste.OrderBy(Function(x) x.Part).ToList() Dim i As Integer = 0 'on commence par voir si des nouvelles valeurs existent avant le début de la liste principale While Liste(i).Part.CompareTo(m.Part) = 1 indexMaj += 1 If indexMaj = maj.Count Then 'c'est fini, pas la peine de continuer à boucler sur Liste Exit While Else 'on prend la valeur à mettre à jour suivante m = maj(indexMaj) End If End While Liste.InsertRange(0, maj.Take(indexMaj)) If log Then 'pour vérifier qu'il fait bien le job File.WriteAllLines("ValeursAvant.csv", maj.Take(indexMaj).Select(Function(x) x.ToString())) End If i = indexMaj If log Then 'pour vérifier qu'il fait bien le job File.WriteAllText("ValeursMAJ.csv", "") File.WriteAllText("ValeursInsérées.csv", "") End If 'on partcours ensuite la liste principale à la recherche de mise à jour et d'ajout Do Select Case Liste(i).Part.CompareTo(m.Part) Case -1 'c'est "inférieur" on ppasse à la valeur suite i += 1 'repart directement au début de la boucle Do Continue Do Case 0 'on met à jour la valeur existante Liste(i) = m If log Then 'pour vérifier qu'il fait bien le job File.AppendAllText("ValeursMAJ.csv", $"{m.ToString()}{vbCrLf}") End If Case 1 'cette valeur est manquante, il faut donc l'inserer ici. Liste.Insert(i, m) If log Then 'pour vérifier qu'il fait bien le job File.AppendAllText("ValeursInsérées.csv", $"{m.ToString()}{vbCrLf}") End If End Select 'on passe à la valeur suivante dans la liste des valeurs à mettre à jour indexMaj += 1 If indexMaj = maj.Count Then 'c'est fini, pas la peine de continuer à boucler sur Liste Exit Do Else 'on prend la valeur à mettre à jour suivante m = maj(indexMaj) End If i += 1 Loop While i < Liste.Count 'enfin, on vérifie qu'il ne reste pas des valeurs à ajouter après la fin de la liste principale If indexMaj < maj.Count Then Liste.AddRange(maj.Skip(indexMaj)) If log Then 'pour vérifier qu'il fait bien le job File.WriteAllLines("ValeursAprès.csv", maj.Skip(indexMaj).Select(Function(x) x.ToString())) End If End If End Sub
Voilà, j'ai testé tout ça d'abord et avant tout parce que ça m'a amusé de me confronter à une quantité de données importantes (j'en ai parfois bien plus au travail).
Mais aussi, pour te faire prendre conscience de quelques points
24 août 2023 à 16:05
Par contre, si tu fais "bêtement" comme ça tu vas perdre du temps.
Soit, tu fais tout le fichier à la lecture (comme dans mon exemple) et après, tu traites le string, un split sur CRLF va te donner un tableau ligne par ligne, tu pourras alimenter ton chargement depuis ce tableau au lieu de lire le streamreader ligne par ligne.
Ou alors, tu appliques le replace à l'écriture à la place de faire ton Trim.
Mais ouvrir le fichier, le traiter complétement et le réécrire (comme mon exemple) serait une perte de temps, d'autant que tu le rouvrirais aussitôt.