IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Tri de chaînes de caractères : Optimiser vitesse ?


Sujet :

Langage Delphi

  1. #1
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut Tri de chaînes de caractères : Optimiser vitesse ?
    Bonjour,

    Je cherche des idées permettant éventuellement d'optimiser la vitesse d'exécution de ce code qui trie des chaînes de caractères :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    procedure Tri_Casiers_Str(var DonneesTxt: tStringList; ProfMaxTri: integer; SensDeTri: boolean);
    //        Renvoie DonneesTxt triée dans le sens croissant si SensDeTri = True, sinon décroissant.
    //        Si ProfMaxTri inférieur à length(chaînes) alors les chaînes ne sont triées que sur les ProfMaxTri premiers caractères
    var io, io1, io2: integer;
      CC: array of byte; // Table des valeurs de caractères courants
      CCcount: integer; // Nombre max d'éléments dans la table CC
      TriPremCar: array[0..255] of tStringList; // Table de partitionnement
      ProfTri: integer; // Profondeur courante du tri = indice de colonne
     
      procedure TrierSur1erCaract;
      //        Tri de partitionnement sur premier caractère
      var i, codeCar: integer;
      begin for i := 0 to 255 do TriPremCar[i] := tStringList.create;
        for i := 0 to donneesTxt.count - 1 do
        begin if length(donneesTxt[i]) > 0 then // ici les chaînes éventuellement vides sont ignorées
          begin codeCar := ord(donneesTxt[i][1]);
            TriPremCar[codeCar].Add(donneesTxt[i]);
          end;
        end;
      end;
     
      function ToutesIdem(SL: tStringList): boolean;
      //        Renvoie True si toutes les strings de la sous-liste SL sont identiques
      var i: integer;
      begin if SL.count = 1 then begin Result := True; EXIT; end;
        i := 0; while (i + 1 <= SL.count - 1) and (SL[i] = SL[i + 1]) do inc(i);
        Result := (i = SL.count - 1);
      end;
     
      procedure TriKsurCarSuivants(var donnees: tStringList);
      //        Tri-Casiers pour tris sur caractères dans colonne ProfTri >= 2
      type ind = array of integer;
      var iini: array of ind; // table des indices initiaux des strings ( iini[IndiceCaractère][nbOcc[IndiceCaractère]]:= indice ligne )
        nbOcc: array of integer; // nombre d'occurences d'un même caractère
        i, j: integer;
        SL: tStringList; // Sous-Liste temporaire de strings commençant par des SubStrings identiques
        lgMax: integer; // length-max des chaînes de SL
        carMax, carMin: byte; // Valeurs Max et Min des caractères dans la colonne ProfTri
        car1, car2: integer; // Bornes de parcours selon SensDeTri (integer à cause de car1:=carMin-1 qui devient négatif si carMin=0)
        car, ic: byte;
     
      begin
        // Recherche des valeurs Min et Max des caractères dans colonne ProfTri :
        carMax := 0; carMin := 255;
        for i := 0 to donnees.count - 1 do
        begin if (ProfTri <= ProfMaxTri) and (ProfTri <= length(donnees[i]))
          then CC[i] := ord(donnees[i][ProfTri]) else CC[i] := 0;
          carMax := max(carMax, CC[i]); carMin := min(carMin, CC[i]);
        end;
        // Tri-casiers :
        SetLength(nbOcc, carMax - carMin + 1);
        SetLength(iini, carMax - carMin + 1);
     
        for i := 0 to donnees.count - 1 do
        begin ic := CC[i] - carMin;
          nbOcc[ic] := nbOcc[ic] + 1; // incrémentation du nombre d'occurrences
          SetLength(iini[ic], nbOcc[ic] + 1);
          iini[ic][nbOcc[ic]] := i; // mémorisation de l'indice initial de la ligne correspondante
        end;
        if SensDeTri = True then begin car1 := carMin - 1; car2 := carMax; end
        else begin car1 := carMax + 1; car2 := carMin; end;
        car := car1;
        repeat if SensDeTri = True then inc(car) else dec(car);
          ic := car - carMin;
          if nbOcc[ic] <> 0 then
          begin if nbOcc[ic] = 1 // une seule occurrence donc récup illico des simplets :
            then donneesTxt.Add(donnees[iini[ic][1]])
            else begin // ici plusieurs occurrences :
     
              SL := tStringList.create; lgMax := 0;
              for j := 1 to nbOcc[ic] do
              begin SL.add(donnees[iini[ic][j]]);
                lgMax := max(length(donnees[iini[ic][j]]), lgMax);
              end;
     
              if (ProfTri < ProfMaxTri) and (ProfTri < lgMax) then // Tri sur caractères suivants :
              begin inc(ProfTri); TriKsurCarSuivants(SL); end // On Récurse
              else // Récup résultat du tri :
                if (ProfTri = ProfMaxTri) or (ProfTri = lgMax) or (ProfTri = length(SL[0]) + 1)
                then donneesTxt.AddStrings(SL); // doublons, triplets, etc ou profondeur de tri atteinte
     
              ProfTri := 2;
              SL.free;
            end; // if nbOcc[car-carMin]=1
          end; // if nbOcc[car-carMin]<>0
        until car = car2;
      end; //TriKsurCarSuivants
     
    begin
      TrierSur1erCaract; // Pré-tri de partitionnement de donneesTxt sur premier caractère
      donneesTxt.clear; // donneesTxt : ré-utilisée pour restituer le tri final
     
      // Recherche de CCcount pour dimensionner CC hors boucle de récursivité de TriKsurCarSuivants
      CCcount := 0;
      for io := 0 to 255 do CCcount := max(CCcount, TriPremCar[io].Count);
      SetLength(CC, CCcount);
     
      if SensDeTri = True then begin io1 := -1; io2 := 255; end
      else begin io1 := 256; io2 := 0; end;
      io := io1;
      repeat if SensDeTri = True then inc(io) else dec(io);
        if TriPremCar[io].count <> 0 then
        begin if ToutesIdem(TriPremCar[io]) or (ProfMaxTri = 1)
          then donneesTxt.AddStrings(TriPremCar[io]) // : récup directe d'un lot de doublons, simplets ... ou du tri sur 1er caractère si ProfMaxTri = 1
          else begin ProfTri := 2; TriKsurCarSuivants(TriPremCar[io]); end; // : tri sur caractères suivants
        end;
      until io = io2;
     
      for io := 0 to 255 do TriPremCar[io].free;
    end; // Tri_Casiers_Str
    Au stade actuel j'obtiens les résultats comparatifs suivants :
    - Tri de 20000 Lignes de 250 caractères = 5000000 triés par Tri_Casiers_Str en 47 ms (Hors affichage du résultat)
    - Tri de 20000 Lignes de 250 caractères = 5000000 triés par QuickSort Recursif en 1451 ms (Hors affichage du résultat) : soit 30 fois plus lent
    - Tri de 50000 Lignes de 250 caractères = 12500000 triés par Tri_Casiers_Str en 93 ms (Hors affichage du résultat)
    - Tri de 50000 Lignes de 250 caractères = 12500000 triés par QuickSort Recursif en 3697 ms (Hors affichage du résultat) : soit 39 fois plus lent

    Mais peut-être qu'il se trouve quelque part une lourdeur dans ce code dont la modification permettrait d'améliorer la vitesse???

    A toutes fins utiles voici le Zip pour tester.

    A+.
    Fichiers attachés Fichiers attachés
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  2. #2
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    un test rapide comme ça

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
     
    procedure Tri_Casiers2_Str(var DonneesTxt: tStringList; ProfMaxTri: integer; SensDeTri: boolean);
    type
      PStringItem = ^TStringItem;
      TStringItem = record
        Value: string;
        Next : PStringItem;
      end;
      TCharList = array[#0..#255] of PStringItem;
     
      function FillList(List: TStringList; Index: Integer; const Items: TCharList; CharPos: Integer): Integer;
      var
        SubList  : TCharList;
        FirstChar: Char;
        NthChar  : Char;
        Item     : PStringItem;
        Next     : PStringItem;
      begin
        Result := 0;
        for FirstChar := #0 to #255 do
        begin
          Item := Items[FirstChar];
          if Item = nil then
            Continue;
          if Item.Next = nil then
          begin
            List[Index] := Item.Value;
            Inc(Index);
            Continue;
          end;
          FillChar(SubList, SizeOf(SubList), 0);
          repeat
            NthChar := Item.Value[CharPos];
            Next := Item.Next;
            Item.Next := SubList[NthChar];
            SubList[NthChar] := Item;
            Item := Next;
          until Item = nil;
          Index := FillList(List, Index, SubList, CharPos + 1);
        end;
        Result := Index;
      end;
     
    var
      AllItems : array of TStringItem;
      FirstList: TCharList;
      Index    : Integer;
      Str      : string;
      FirstChar: Char;
      Item     : PStringItem;
    begin
      SetLength(AllItems, DonneesTxt.Count);
      FillChar(FirstList, SizeOf(FirstList), 0);
      for Index := 0 to DonneesTxt.Count - 1 do
      begin
        Str := DonneesTxt[Index];
        if Str <> '' then
        begin
          Item := @AllItems[Index];
          Item.Value := Str;
          FirstChar := Str[1];
          Item.Next := FirstList[FirstChar];
          FirstList[FirstChar] := Item;
        end;
      end;
      Index := FillList(DonneesTxt, 0, FirstList, 2);
      while DonneesTxt.Count > Index do
        DonneesTxt.Delete(Index + 1);
    end;
    NB: j'ai zappé la profondeur et l'ordre mais c'est pas le plus compliqué à ajouter

    L'idée est qu'en utilisant une liste chaînée je limite au maximum (ou au minimum ?) les allocations mémoire qui coûte cher.

    NB: dans ton code la liste SL devrait être allouée en dehors de la boucle
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  3. #3
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Merci Paul TOTH.

    Tri_Casiers2_Str : Plus que 69 lignes c'est bigrement simplifié !!!

    NB: j'ai zappé la profondeur et l'ordre mais c'est pas le plus compliqué à ajouter

    L'idée est qu'en utilisant une liste chaînée je limite au maximum (ou au minimum ?) les allocations mémoire qui coûte cher.

    NB: dans ton code la liste SL devrait être allouée en dehors de la boucle
    OK, mille fois merci. Par contre je vais tester ton code demain car pour l'instant je vais rattraper mon repas de midi (scotché à ma bécane depuis ce matin).

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  4. #4
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    attention, il doit y avoir quelques cas limites à tester...je pensais par exemple faire l'économie du test de longueur de chaîne car le #0 terminal permet de distinguer 'ab' et 'abc'...mais si la liste contient deux fois la même chaîne, le 0 terminal sera ignoré et l'itération suivante testera un caractère au delà de la fin de chaîne

    il faut le gérer avec quelque chose comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
        FillChar(SubList, SizeOf(SubList), 0);
          repeat
            Next := Item.Next;
            if CharPos > Length(Item.Value) then
            begin
              List[Index] := Item.Value;
              Inc(Index);
            end else begin
              NthChar := Item.Value[CharPos];
              Item.Next := SubList[NthChar];
              SubList[NthChar] := Item;
            end;
            Item := Next;
          until Item = nil;
          Index := FillList(List, Index, SubList, CharPos + 1);
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  5. #5
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Bonjour,

    Paul TOTH : attention, il doit y avoir quelques cas limites à tester...je pensais par exemple faire l'économie du test de longueur de chaîne car le #0 terminal permet de distinguer 'ab' et 'abc'...mais si la liste contient deux fois la même chaîne, le 0 terminal sera ignoré et l'itération suivante testera un caractère au delà de la fin de chaîne

    il faut le gérer avec quelque chose comme ceci:
    OK, merci, je vais tester tout ceci ce matin.

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  6. #6
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Paul TOTH : ... cas limites à tester...mais si la liste contient deux fois la même chaîne, le 0 terminal sera ignoré et l'itération suivante testera un caractère au delà de la fin de chaîne ... il faut le gérer avec quelque chose comme ceci
    J'ai tenu compte de cette modification et fait des tests avec des listes contenant 3 fois la même chaîne et de longueur différente des autres : le résultat a été correct.

    Résultats comparatifs de tests de vitesse :

    - Tri de 20000 Lignes de 250 caractères = 5000000 triés par Tri_Casiers2_Str en 16 ms (Hors affichage du résultat)
    - Tri de 20000 Lignes de 250 caractères = 5000000 triés par Tri_Casiers_Str en 47 ms (Hors affichage du résultat) : soit 3 fois plus lent que Tri_Casiers2_Str
    - Tri de 20000 Lignes de 250 caractères = 5000000 triés par QuickSort Recursif en 1451 ms (Hors affichage du résultat) : soit 90 fois plus lent que Tri_Casiers2_Str

    - Tri de 50000 Lignes de 250 caractères = 12500000 triés par Tri_Casiers2_Str en 31 ms (Hors affichage du résultat)
    - Tri de 50000 Lignes de 250 caractères = 12500000 triés par Tri_Casiers_Str en 93 ms (Hors affichage du résultat): soit 3 fois plus lent que Tri_Casiers2_Str
    - Tri de 50000 Lignes de 250 caractères = 12500000 triés par QuickSort Recursif en 3697 ms (Hors affichage du résultat) : soit 119 fois plus lent que Tri_Casiers2_Str

    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers2_Str en 47 ms (Hors affichage du résultat)
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers_Str en 172 ms (Hors affichage du résultat) soit 3,7 fois plus lent que Tri_Casiers2_Str
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec QuickSort Recursif en 141290 ms (Hors affichage du résultat) soit 3006 fois plus lent que Tri_Casiers2_Str !!!

    On dirait que QuickSort reste coincé dans les starting-blocs au démarrage !!!.

    Bon il ne reste plus qu'à trouver à quel endroit du code je peux placer mon instruction préférée nommée EXIT pour quitter au plus vite la routine lorsque la profondeur utile de tri est atteinte car il y a de nombreux cas où il n'est pas nécessaire de boucler sur toute la longueur des chaînes, ... et cela améliorera encore un peu les perfs.

    En tous cas chapeau pour ce premier gain de vitesse : t'es un chef

    A+.

    EDIT du 23/10/2013: Rectification d'une erreur entachant les résultats des tests de vitesse ci-dessus :

    Pour profondeur de tri = 250 :
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers2_Str (StringList) en 47 ms (Hors affichage du résultat)
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers3_StrBis (IndexesOrdreTri) en 31 ms (Hors affichage du résultat)
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec QuickSort Recursif en 10312 ms (Hors affichage du résultat)
    Donc QuickSort Recursif :
    - 219 fois plus lent que Tri_Casiers2_Str (au lieu des 3006 !!!)
    - et 332 fois plus lent que Tri_Casiers3_StrBis
    Ceci n'empêche toutefois pas le fait qu'une fois qu'on a goûté à la vitesse du Tri-Casiers on trouve le QuickSort interminablement lent.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  7. #7
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    c'est CharPos qui donne la profondeur d'analyse, il y a déjà un "CharPos > Length(Str)" il suffit d'ajouter un test de profondeur au même endroit
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  8. #8
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Paul TOTH : c'est CharPos qui donne la profondeur d'analyse, il y a déjà un "CharPos > Length(Str)" il suffit d'ajouter un test de profondeur au même endroit
    OK, mais en quittant la sous-routine FillList comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    FillChar(SubList, SizeOf(SubList), 0);
          repeat
            Next := Item.Next;
            if CharPos > ProfMaxTri then begin
              NthChar := Item.Value[CharPos];
              Item.Next := SubList[NthChar];
              SubList[NthChar] := Item;
              Index := FillList(List, Index, SubList, CharPos + 1);
              Result := Index; EXIT;
            end;
            if CharPos > Length(Item.Value) then
            begin
              List[Index] := Item.Value;
              Inc(Index);
            end else begin
              NthChar := Item.Value[CharPos];
              Item.Next := SubList[NthChar];
              SubList[NthChar] := Item;
            end;
            Item := Next;
          until Item = nil;
          Index := FillList(List, Index, SubList, CharPos + 1);
        end;
        Result := Index;
      end; // FillList
    le compilo me répond "EStringListError" avec le message "Indice de liste hors limite"

    Je suppose qu'avant l'EXIT il faut d'abord finaliser autrement ???

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  9. #9
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    euh...ben y'a pas à faire Exit, surtout qu'il faut traiter les éléments "Next"

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
            if (CharPos > ProxMaxTri) or (CharPos > Length(Item.Value)) then
            begin
              List[Index] := Item.Value;
              Inc(Index);
            end else begin
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  10. #10
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour Paul TOTH,

    J'ai rectifié suivant tes indications et ça marche au poil.

    J'ai vite fait un test comparatif de vitesses :
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers2_Str et Profondeur de Tri = 250 : 47 ms (Hors affichage du résultat)
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers2_Str et Profondeur deTri = 25 : 47 ms (Hors affichage du résultat)

    Bizarre : je ne comprends pas pourquoi on ne gagne pas en vitesse en limitant le tri aux 10% premiers caractères ??? Il y a un truc qui m'échappe.

    En tous cas on saura que QuickSort est 3006 fois plus lent que Tri_Casiers2_Str dans les mêmes conditions de tests!!!

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  11. #11
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    Citation Envoyé par Gilbert Geyer Voir le message
    Re-bonjour Paul TOTH,

    J'ai rectifié suivant tes indications et ça marche au poil.

    J'ai vite fait un test comparatif de vitesses :
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers2_Str et Profondeur de Tri = 250 : 47 ms (Hors affichage du résultat)
    - Tri de 100000 Lignes de 250 caractères = 25000000 avec Tri_Casiers2_Str et Profondeur deTri = 25 : 47 ms (Hors affichage du résultat)

    Bizarre : je ne comprends pas pourquoi on ne gagne pas en vitesse en limitant le tri aux 10% premiers caractères ??? Il y a un truc qui m'échappe.

    En tous cas on saura que QuickSort est 3006 fois plus lent que Tri_Casiers2_Str dans les mêmes conditions de tests!!!

    A+.
    probablement car la profondeur 25 est suffisante pour trier toutes les chaînes
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  12. #12
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Bonjour,

    Paul TOTH :probablement car la profondeur 25 est suffisante pour trier toutes les chaînes
    Je viens de faire un autre test :
    Au lieu de trier 100000 Lignes de 250 caractères avec une profondeur de tri de 25 caractères j'ai fait l'inverse en triant :
    - 250 lignes de 100000 caractères avec une profondeur de tri de 100000,
    - puis 250 lignes de 100000 caractères avec une profondeur de tri de 25.
    Dans les deux cas le Delta de GetTickCount m'a affiché 0 ms.

    Tout ça provient à mon avis du principe même du tri-casiers qui fait que la plus grosse part du boulot s'effectue lors du pré-tri de partitionnement sur le premier caractère et que les tris sur les caractères suivants ne modifient les indices des chaînes que d'un chouilla ... tandis que le QuikSort peine à permuter les lignes 2 par 2 et à re-permuter certaines lignes dans sa progression.

    Pour ce qui est d'ajouter la possibilité d'obtenir un tri décroissant dans le code de Tri_Casiers2_Str je me demande si ça vaut le coup car ce besoin n'apparaît à mon avis que pour visualiser le résultat autrement et pour l'affichage il suffit de lire la StringList de la dernière ligne en remontant vers la première.

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  13. #13
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Voiçi pour résumer le code finalement utilisé, pour le cas où quelqu'un serait intéressé par ses performances.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    procedure Tri_Casiers2_Str(var DonneesTxt: tStringList; ProfMaxTri: integer);
    type
      PStringItem = ^TStringItem;
      TStringItem = record
        Value: string;
        Next: PStringItem;
      end;
      TCharList = array[#0..#255] of PStringItem;
     
      function FillList(List: TStringList; Index: Integer; const Items: TCharList; CharPos: Integer): Integer;
      var
        SubList: TCharList;
        FirstChar: Char;
        NthChar: Char;
        Item: PStringItem;
        Next: PStringItem;
      begin
        Result := 0;
        for FirstChar := #0 to #255 do // Tri Croissant
        //for FirstChar := #255 downto #0 do // Tri Décroissant
        begin
          Item := Items[FirstChar];
          if Item = nil then Continue;
          if Item.Next = nil then
          begin
            List[Index] := Item.Value;
            Inc(Index);
            Continue;
          end;
          FillChar(SubList, SizeOf(SubList), 0);
          repeat
            Next := Item.Next;
            if (CharPos > ProfMaxTri) or (CharPos > Length(Item.Value)) then
            begin
              List[Index] := Item.Value;
              Inc(Index);
            end else begin
              NthChar := Item.Value[CharPos];
              Item.Next := SubList[NthChar];
              SubList[NthChar] := Item;
            end;
            Item := Next;
          until Item = nil;
          Index := FillList(List, Index, SubList, CharPos + 1);
        end;
        Result := Index;
      end; // FillList
     
    var
      AllItems: array of TStringItem;
      FirstList: TCharList;
      Index: Integer;
      Str: string;
      FirstChar: Char;
      Item: PStringItem;
    begin
      SetLength(AllItems, DonneesTxt.Count);
      FillChar(FirstList, SizeOf(FirstList), 0);
      for Index := 0 to DonneesTxt.Count - 1 do
      begin
        Str := DonneesTxt[Index];
        if Str <> '' then
        begin
          Item := @AllItems[Index];
          Item.Value := Str;
          FirstChar := Str[1];
          Item.Next := FirstList[FirstChar];
          FirstList[FirstChar] := Item;
        end;
      end;
      Index := FillList(DonneesTxt, 0, FirstList, 2);
      while DonneesTxt.Count > Index do
        DonneesTxt.Delete(Index + 1);
    end; // Tri_Casiers2_Str
    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  14. #14
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    pour un tri décroissant il est aussi possible d'alimenter la liste par Index décroissant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
      List[Index] := Item.Value;
      Inc(Index, SensDeTri); // où SensDeTri = +1 ou -1
    il faudra bien évidemment correctement choisir la valeur initiale de Index
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  15. #15
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Paul TOTH : pour un tri décroissant il est aussi possible d'alimenter la liste par Index décroissant
    il faudra bien évidemment correctement choisir la valeur initiale de Index :
    Voiçi le code modifié : ça marche, mille fois merci.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    procedure Tri_Casiers2_Str(var DonneesTxt: tStringList; ProfMaxTri: integer; TriCroissant: boolean);
    type
      PStringItem = ^TStringItem;
      TStringItem = record
        Value: string;
        Next: PStringItem;
      end;
      TCharList = array[#0..#255] of PStringItem;
    
      function FillList(List: TStringList; Index: Integer; const Items: TCharList; CharPos: Integer): Integer;
      var
        SubList: TCharList;
        FirstChar: Char;
        NthChar: Char;
        Item: PStringItem;
        Next: PStringItem;
        SensDeTri: shortInt;
      begin
        Result := 0;
        if TriCroissant then SensDeTri := +1 else SensDeTri := -1;
        for FirstChar := #0 to #255 do
        begin
          Item := Items[FirstChar];
          if Item = nil then Continue;
          if Item.Next = nil then
          begin
            List[Index] := Item.Value;
            Inc(Index, SensDeTri);
            Continue;
          end;
          FillChar(SubList, SizeOf(SubList), 0);
          repeat
            Next := Item.Next;
            if (CharPos > ProfMaxTri) or (CharPos > Length(Item.Value)) then
            begin
              List[Index] := Item.Value;
              Inc(Index, SensDeTri);
            end else begin
              NthChar := Item.Value[CharPos];
              Item.Next := SubList[NthChar];
              SubList[NthChar] := Item;
            end;
            Item := Next;
          until Item = nil;
          Index := FillList(List, Index, SubList, CharPos + 1);
        end;
        Result := Index;
      end; // FillList
    
    var
      AllItems: array of TStringItem;
      FirstList: TCharList;
      Index: Integer;
      Str: string;
      FirstChar: Char;
      Item: PStringItem;
    begin
      SetLength(AllItems, DonneesTxt.Count);
      FillChar(FirstList, SizeOf(FirstList), 0);
      for Index := 0 to DonneesTxt.Count - 1 do
      begin
        Str := DonneesTxt[Index];
        if Str <> '' then
        begin
          Item := @AllItems[Index];
          Item.Value := Str;
          FirstChar := Str[1];
          Item.Next := FirstList[FirstChar];
          FirstList[FirstChar] := Item;
        end;
      end;
      if TriCroissant then begin
        Index := FillList(DonneesTxt, 0, FirstList, 2);
        while DonneesTxt.Count > Index do
          DonneesTxt.Delete(Index + 1);
      end
      else Index := FillList(DonneesTxt, DonneesTxt.Count - 1, FirstList, 2); 
    end; // Tri_Casiers2_Str
    A quoi sert le Delete en fin de code ??? :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    while DonneesTxt.Count > Index do
          DonneesTxt.Delete(Index + 1);
    ça m'intrigue car en neutralisant ces deux instructions le code fonctionne correctement.

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  16. #16
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    ça n'a d'intérêt que si la liste contient des chaînes vides. Elles sont ignorée dans la première boucle, il faut donc les supprimer de la liste résultat...ce qui peut se faire avant le tri, le but c'est juste d'éviter de vider et recréer la liste alors qu'elle contient déjà suffisamment de place pour tous les éléments...d'ailleurs ça doit pas être Index + 1 mais Index l'indice de suppression je pense
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  17. #17
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Bonjour,

    Paul TOTH :ça n'a d'intérêt que si la liste contient des chaînes vides. Elles sont ignorée dans la première boucle, il faut donc les supprimer de la liste résultat...ce qui peut se faire avant le tri, le but c'est juste d'éviter de vider et recréer la liste alors qu'elle contient déjà suffisamment de place pour tous les éléments...d'ailleurs ça doit pas être Index + 1 mais Index l'indice de suppression je pense
    OK, merci, pigé, donc il faut conserver ce Delete.

    J'ai aussi posé cette question pour la raison suivante :

    Actuellement ce Delete n'agit que lors d'un tri Croissant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    if TriCroissant then begin
        Index := FillList(DonneesTxt, 0, FirstList, 2);
        while DonneesTxt.Count > Index do
          DonneesTxt.Delete(Index + 1);
      end
      else Index := FillList(DonneesTxt, DonneesTxt.Count - 1, FirstList, 2);
    ... car lors d'un tri Décroissant ça m'a déclenché le message d'erreur : "EStringListError" avec le message "Indice de liste hors limite".
    Mais comme on peut aussi avoir des chaînes vides lors d'un tri décroissant je pense que dans ce cas il faut le faire avec un DonneesTxt.Delete(Index).
    Bon je vais essayer ceci, en effectuant ce nettoyage à la fin car le "faire avant le tri" ça plomberait les performances vu que chaque Delete intermédiaire engendre par effet domino dans la StringList le transfert de toutes les chaînes suivantes d'un cran vers l'avant.

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  18. #18
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Au final voici le code corrigé pour éliminer le problème des chaînes vides :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    procedure Tri_Casiers2_Str(var DonneesTxt: tStringList; ProfMaxTri: integer; TriCroissant: boolean);
    type
      PStringItem = ^TStringItem;
      TStringItem = record
        Value: string;
        Next: PStringItem;
      end;
      TCharList = array[#0..#255] of PStringItem;
    
      function FillList(List: TStringList; Index: Integer; const Items: TCharList; CharPos: Integer): Integer;
      var
        SubList: TCharList;
        FirstChar: Char;
        NthChar: Char;
        Item: PStringItem;
        Next: PStringItem;
        SensDeTri: shortInt;
      begin
        Result := 0;
        if TriCroissant then SensDeTri := +1 else SensDeTri := -1;
        for FirstChar := #0 to #255 do
        begin
          Item := Items[FirstChar];
          if Item = nil then Continue;
          if Item.Next = nil then
          begin
            List[Index] := Item.Value;
            Inc(Index, SensDeTri);
            Continue;
          end;
          FillChar(SubList, SizeOf(SubList), 0);
          repeat
            Next := Item.Next;
            if (CharPos > ProfMaxTri) or (CharPos > Length(Item.Value)) then
            begin
              List[Index] := Item.Value;
              //Inc(Index);
              Inc(Index, SensDeTri);
            end else begin
              NthChar := Item.Value[CharPos];
              Item.Next := SubList[NthChar];
              SubList[NthChar] := Item;
            end;
            Item := Next;
          until Item = nil;
          Index := FillList(List, Index, SubList, CharPos + 1);
        end;
        Result := Index;
      end; // FillList
    
    var
      AllItems: array of TStringItem;
      FirstList: TCharList;
      Index: Integer;
      Str: string;
      FirstChar: Char;
      Item: PStringItem;
    begin
      SetLength(AllItems, DonneesTxt.Count);
      FillChar(FirstList, SizeOf(FirstList), 0);
      for Index := 0 to DonneesTxt.Count - 1 do
      begin
        Str := DonneesTxt[Index];
        if Str <> '' then
        begin
          Item := @AllItems[Index];
          Item.Value := Str;
          FirstChar := Str[1];
          Item.Next := FirstList[FirstChar];
          FirstList[FirstChar] := Item;
        end;
      end;
      if TriCroissant then begin
        Index := FillList(DonneesTxt, 0, FirstList, 2);
        while DonneesTxt.Count > Index do DonneesTxt.Delete(Index);
      end
      else begin
        Index := FillList(DonneesTxt, DonneesTxt.Count - 1, FirstList, 2);
        while Index >= 0 do begin DonneesTxt.Delete(0); dec(Index); end;
      end;
    end; // Tri_Casiers2_Str
    En tous cas, mille fois merci car ma version initiale s'est avérée être 3,7 fois plus lente que Tri_Casiers2_Str.

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

  19. #19
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 430
    Points
    28 430
    Par défaut
    oui, mais tu peux aussi l'écrire comme ça
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
      while DonneesTxt.Count > Index do DonneesTxt.Delete(Index);
      if TriCroissant then begin
        Index := FillList(DonneesTxt, 0, FirstList, 2);
      end
      else begin
        Index := FillList(DonneesTxt, DonneesTxt.Count - 1, FirstList, 2);
      end;
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  20. #20
    Modérateur

    Homme Profil pro
    Ingénieur retraité
    Inscrit en
    Octobre 2005
    Messages
    2 396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur retraité

    Informations forums :
    Inscription : Octobre 2005
    Messages : 2 396
    Points : 3 263
    Points
    3 263
    Par défaut
    Re-bonjour,

    Paul TOTH : oui, mais tu peux aussi l'écrire comme ça ...
    Oui mais ça ne marche pas correctement car vérification faite, avec la liste réduite ci-après :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    ListeATrier := tStringList.Create;
    ListeATrier.Add('qsjkqmk');
    ListeATrier.Add('zreurzuir');
    ListeATrier.Add('mdjgssdv');
    ListeATrier.Add('ehkhhkkhhg');
    ListeATrier.Add('adhfrsvk');
    ListeATrier.Add('');
    ListeATrier.Add('');
    Le résultat du tri Croissant fait apparaître dans la ListBox deux lignes vides à la fin.

    Et le résultat du tri Décroissant donne ceci où les deux lignes vides sont remontées au début et remplacées par deux résidus formant des doublons avec la suite :
    qsjkqmk
    zreurzuir

    zreurzuir
    qsjkqmk
    mdjgssdv
    ehkhhkkhhg
    adhfrsvk
    Donc, il vaut mieux ne pas changer.

    A+.
    N'oubliez pas de consulter les FAQ Delphi et les cours et tutoriels Delphi

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 3 123 DernièreDernière

Discussions similaires

  1. Tri de chaîne de caractére
    Par lekaf974 dans le forum Langage
    Réponses: 6
    Dernier message: 19/03/2013, 01h07
  2. Tri de chaînes de caractères
    Par zizou.r23 dans le forum Débuter
    Réponses: 4
    Dernier message: 11/02/2013, 11h56
  3. [XL-2007] Mise en rouge et en gras d'un chaîne de caractère / optimisation
    Par FanTasTik dans le forum Macros et VBA Excel
    Réponses: 5
    Dernier message: 29/08/2012, 16h48
  4. Tri de chaîne de caractère
    Par sk8trasher dans le forum Débuter
    Réponses: 12
    Dernier message: 28/06/2012, 08h00
  5. tri par corrélation entre chaînes de caractères
    Par petitmic dans le forum Langage SQL
    Réponses: 7
    Dernier message: 09/09/2005, 15h15

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo