Partage
  • Partager sur Facebook
  • Partager sur Twitter

[C# 4.0] random char pas si random

Sujet résolu
    21 mars 2012 à 2:34:34

    Bonjour, je me suis fait un petit logiciel pour un projet personnel de graphisme. Celui-ci consiste a créé une suite de 8 chars composés de 0 et 1 binaire de 13 lignes. Ça cava, mais ça donne ça :
    000000001110011
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    011011011010010
    on voit que les lignes ce ressemble beaucoup j'aimerais qu'il soie plus aléatoire.

    Voici mon code Csharp:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace binaryGen
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
    
            string[] line = new string[13];
            int y;
    
            private void Generater()
            {
                var binary = "01";
                var random = new Random();
                var resultat = new string(
                    Enumerable.Repeat(binary, 15)
                    .Select(s => s[random.Next(s.Length)])
                    .ToArray());
                line[y] = resultat;
            }
    
    
            private void BT_GEN_Click(object sender, EventArgs e)
            {
                for (y = 0; y < 13; y++)
                {
                    Generater();
                        
                    
                    TB_OUT.Lines = line ;
                }
            }
        }
    }
    


    Merci
    • Partager sur Facebook
    • Partager sur Twitter
      21 mars 2012 à 9:28:33

      Bonjour,

      Sors l'instanciation du Random de ta fonction et ça marchera beaucoup mieux ;)
      Il faut comprendre qu'à sa création, une instance de Random se sert de l'heure système pour initialiser sa séquence de nombres pseudos aléatoires.
      Or les différentes itérations de ta méthode Générater (Générer serait plus français) se produisent tellement vite que tout tes Random sont initialisés avec la même heure donc avec la même séquence de nombres.


      De plus utiliser y et line comme membre de la classe et peu utile (judicieux) il vaudrait mieux (pour y) le déclarer localement et faire en sorte (pour line) que ta méthode renvoie un char[] (ou un string[] ou un string ou autre).
      Au passage tes lignes ne font pas 8 mais 15 caractères :-°

      Exemple (avec une méthode de génération légèrement différente):
      private static readonly Random random = new Random();
      private string BinDigits(int size)
      {
          // comme random.Next() renvoie un nombre compris 0 et int.MaxValue
          // il a (en gros) 1 chance sur 2 d'être pair ou impair
          // donc que le reste de sa division par 2 soit 0 ou 1
          return Enumerable
              .Range(1, size)
              .Aggregate("", (accumulator, ignoreMe) => accumulator + random.Next() % 2);
      }
      
      // ensuite on peut récupérer ses lignes comme ceci
      string lines = Enumerable
          .Range(1, 13)
          .Aggregate("", (accumulator, ignoreMe) => accumulator + BinDigits(15) + Environment.NewLine);
      


      Exemple de résultat :

      Citation : Exécution du code

      011001010100001
      000101101000010
      001010001111011
      100010011110100
      001101010010010
      101111010101010
      000111000001101
      010000001110010
      000001100101000
      011110111011001
      111000011001111
      011110110000110
      110110000111110



      On pourrait aussi faire une séquence infinie de chiffres binaires (à l'aide d'un block itérateur) :
      private static readonly Random random = new Random();
      private IEnumerable<int> BinDigits()
      {
          while (true)
          {
              yield return random.Next() % 2;
          }
      }
      
      // pour récupérer ses lignes :
      string lines = Enumerable
          .Range(1, 13)
          .Aggregate("", (accLines, ignore) =>
              accLines + BinDigits()
                  .Take(15) // ne surtout pas l'oublier sinon on récupère des chiffres longtemps
                  .Aggregate("", (accLine, currentDigit) =>
                      accLine + currentDigit) + Environment.NewLine);
      

      J'ai volontairement épargné les versions avec StringBuilder (dans le cas où il y a beaucoup de concaténations) par souci de simplicité.

      En parlant de simplicité on peut aussi bien faire (sans StringBuilder toujours)
      private string BinDigitsGrid(int rows, int columns)
      {
          // ici on "pourrait" mettre le random ici parce qu'il n'est pas réinitialisé pendant l'exécution de toute la grille
          // mais attention si la méthode doit être appelée fréquemment (risque de recréer le problème initial)
          Random random = new Random();
          string result = "";
      
          for (int r = 0; r < rows; ++r)
          {
              for (int c = 0; c < columns; ++c)
              {
                  result += random.Next() % 2;
              }
              result += Environment.NewLine;
          }
          return result;
      }
      
      // exemple d'utilisation
      string lines = BinDigitsGrid(13, 15);
      


      Bon on pourrait encore jouer longtemps à trouver d'autres méthodes :p

      Cordialement !

      Arf je fais un gros pâté et voilà qu'entre temps je me fais griller :euh:
      • Partager sur Facebook
      • Partager sur Twitter
      Censément, quelqu'un de sensé est censé s'exprimer sensément.
        21 mars 2012 à 12:30:50

        Ouch Sehnsucht, les Aggregate() pour concaténer des strings c'est élégant mais très peu performant - ça crée des strings intermédiaires en pagaille. Utilise plutôt la méthode string.Join() pour avoir le même résultat - dommage qu'il n'y ait pas nativement d'extension JoinString() pour les IEnumerable<>, mais elle est facile à créer.

        Même problème dans ta méthode BinDigitsGrid() : ça parait plus simple (et encore), mais en terme de perfs c'est catastrophique. Les StringBuilders ont une utilité bien réelle. ;)

        La solution préconisée serait plutôt celle-ci :

        private string BinDigitsGrid(int rows, int columns)
        {
            // ici on "pourrait" mettre le random ici parce qu'il n'est pas réinitialisé pendant l'exécution de toute la grille
            // mais attention si la méthode doit être appelée fréquemment (risque de recréer le problème initial)
            Random random = new Random();
            StringBuilder result = new StringBuilder();
        
            for (int r = 0; r < rows; ++r)
            {
                for (int c = 0; c < columns; ++c)
                {
                    result.Append(random.Next() % 2);
                }
                result.Append(Environment.NewLine);
            }
            return result.ToString();
        }
        
        // exemple d'utilisation
        string lines = BinDigitsGrid(13, 15);
        


        Ou encore, pour avoir une plus grande flexibilité :

        private void BinDigitsGrid(TextWriter writer, int rows, int columns)
        {
            // ici on "pourrait" mettre le random ici parce qu'il n'est pas réinitialisé pendant l'exécution de toute la grille
            // mais attention si la méthode doit être appelée fréquemment (risque de recréer le problème initial)
            Random random = new Random();
            
            for (int r = 0; r < rows; ++r)
            {
                for (int c = 0; c < columns; ++c)
                {
                    writer.Write(random.Next() % 2);
                }
                writer.WriteLine();
            }
        }
        
        // exemples d'utilisation :
        //1) pour avoir une string
        StringBuilder builder = new StringBuilder();
        BinDigitsGrid(new StringWriter(builder), 13, 15);
        string result = builder.ToString();
        
        //2) pour écrire directement dans la console
        BinDigitsGrid(Console.Out, 13, 15);
        
        //3) pour écrire directement dans un fichier
        using(var writer = new StreamWriter("output.txt"))
            BinDigitsGrid(writer, 13, 15);
        
        • Partager sur Facebook
        • Partager sur Twitter
          21 mars 2012 à 14:01:44

          J'ai quand même bien précisé que je mettais pas les StringBuilder par souci de simplicité (sinon c'est clair que ça plombe pas mal les choses)
          Par contre entre String.Join et Aggregate(new StringBuilder() ...) il y a beaucoup de différences ?
          • Partager sur Facebook
          • Partager sur Twitter
          Censément, quelqu'un de sensé est censé s'exprimer sensément.
            21 mars 2012 à 14:46:08

            Citation : Sehnsucht

            J'ai quand même bien précisé que je mettais pas les StringBuilder par souci de simplicité (sinon c'est clair que ça plombe pas mal les choses)


            En effet, mais la solution la plus simple n'est pas forcément la meilleure à présenter et tu aurais pu évoquer les problèmes de perfs. ;)

            Citation : Sehnsucht

            Par contre entre String.Join et Aggregate(new StringBuilder() ...) il y a beaucoup de différences ?


            Bonne question, il faudrait faire un benchmark pour comparer :euh:
            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              21 mars 2012 à 17:14:47

              Pour rire...
              Aggregate with string.Empty     92 ticks
              Aggregate with StringBuilder    15 ticks
              Join with Select                38 ticks
              For loop with StringBuilder     13 ticks
              For loop with List<char>        39 ticks


              Code des méthodes :
              private static string GetRandomString_StringAggregate( int length )
              {
                  return Enumerable.Range( 0, length ).Aggregate( string.Empty, ( s, _ ) => s + GetRandomChar() );
              }
              
              private static string GetRandomString_BuilderAggregate( int length )
              {
                  return Enumerable.Range( 0, length ).Aggregate( new StringBuilder(), ( b, _ ) => b.Append( GetRandomChar() ) ).ToString();
              }
              
              private static string GetRandomString_JoinSelect( int length )
              {
                  return string.Join( string.Empty, Enumerable.Range( 0, length ).Select( n => GetRandomChar() ) );
              }
              
              private static string GetRandomString_ForBuilder( int length )
              {
                  var builder = new StringBuilder();
                  for ( int n = 0; n < length; n++ )
                  {
                      builder.Append( GetRandomChar() );
                  }
                  return builder.ToString();
              }
              
              private static string GetRandomString_ForCharList( int length )
              {
                  var list = new List<char>();
                  for ( int n = 0; n < length; n++ )
                  {
                      list.Add( GetRandomChar() );
                  }
                  return string.Join( string.Empty, list );
              }
              
              private static char GetRandomChar()
              {
                  return (char) RandGen.Next( 'A', 'z' );
              }
              


              Code du benchmark :
              private static void Main( string[] args )
              {
                  const int iterations = 50000;
                  const int length = 150;
                  var actions = new Dictionary<string, Action>
                  {
                      { "Aggregate with string.Empty", () => GetRandomString_StringAggregate(length) },
                      { "Aggregate with StringBuilder", () => GetRandomString_BuilderAggregate(length) },
                      { "Join with Select", () => GetRandomString_JoinSelect(length) },
                      { "For loop with StringBuilder", () => GetRandomString_ForBuilder(length) },
                      { "For loop with List<char>", () => GetRandomString_ForCharList(length) }
                  };
              
                  Benchmark( actions, iterations );
              
                  Console.WriteLine( "Done." );
                  Console.ReadKey();
              }
              
              private static void Benchmark( Dictionary<string, Action> actions, int iterations )
              {
                  foreach ( var pair in actions )
                  {
                      var ticks = Benchmark( pair.Value, iterations );
                      Console.WriteLine( "{0,-30} {1,3} ticks", pair.Key, ticks );
                  }
              }
              
              // gets number of ticks from method
              private static long Benchmark( Action action, int iterations )
              {
                  long totalTicks = 0;
                  var watch = new Stopwatch();
              
                  for ( int n = 0; n < iterations; n++ )
                  {
                      watch.Restart();
                      action();
                      watch.Stop();
                      totalTicks += watch.ElapsedTicks;
                  }
              
                  return totalTicks / iterations;
              }
              
              • Partager sur Facebook
              • Partager sur Twitter
                21 mars 2012 à 19:23:47

                Intéressant :) Pour bien faire il aurait aussi fallu comparer le comportement du GC.... :p
                • Partager sur Facebook
                • Partager sur Twitter
                Anonyme
                  21 mars 2012 à 19:46:34

                  Je te laisse le faire, je sais pas vraiment comment on fait ça. :-°
                  • Partager sur Facebook
                  • Partager sur Twitter
                    21 mars 2012 à 20:31:53

                    Merci à tous, je ne pensais pas provoquer au temps de controverse et de réponse. Je vais faire des tests avec vos idées.

                    J'ai utilisé cette méthode:
                    private string BinDigitsGrid(int rows, int columns)
                    {
                        // ici on "pourrait" mettre le random ici parce qu'il n'est pas réinitialisé pendant l'exécution de toute la grille
                        // mais attention si la méthode doit être appelée fréquemment (risque de recréer le problème initial)
                        Random random = new Random();
                        StringBuilder result = new StringBuilder();
                    
                        for (int r = 0; r < rows; ++r)
                        {
                            for (int c = 0; c < columns; ++c)
                            {
                                result.Append(random.Next() % 2);
                            }
                            result.Append(Environment.NewLine);
                        }
                        return result.ToString();
                    }
                    
                    // exemple d'utilisation
                    string lines = BinDigitsGrid(13, 15);
                    


                    Que j'ai adapté à ma windowform ça fonctionne très bien merci encore.
                    • Partager sur Facebook
                    • Partager sur Twitter
                    Anonyme
                      21 mars 2012 à 23:04:10

                      Encore plus rapide : (7 ticks chez moi, moitié moins du for sur StringBuilder)
                      private static string GetRandomString_ForCharArray( int length )
                      {
                          var tab = new char[length];
                          for ( int n = 0; n < length; n++ )
                          {
                              tab[n] = GetRandomChar();
                          }
                          return new string( tab );
                      }
                      
                      • Partager sur Facebook
                      • Partager sur Twitter
                        21 mars 2012 à 23:27:03

                        Totalement pour le fun ! :lol:
                        Ne pas reproduire dans du code de "production"

                        // penser à activer le code unsafe dans les propriétés du projet
                        private static unsafe string GetRandomString_WhileCharPointer(int length)
                        {
                            char* pTab = stackalloc char[length];
                            while (length > 0)
                                pTab[--length] = GetRandomChar();
                            return new string(pTab);
                        }
                        

                        Note: c'est pas dit que ça soit beaucoup plus rapide (un peu peut-être sur le while mais pas plus, et encore si ça se trouve c'est optimisé par le compilo justement)

                        Cordialement !
                        • Partager sur Facebook
                        • Partager sur Twitter
                        Censément, quelqu'un de sensé est censé s'exprimer sensément.
                        Anonyme
                          22 mars 2012 à 12:23:16

                          Effectivement, le JIT enlève les vérifications inutiles donc ton code est plus rapide quand il est lancé depuis VS mais produit le même résultat hors de VS.
                          • Partager sur Facebook
                          • Partager sur Twitter

                          [C# 4.0] random char pas si random

                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                          • Editeur
                          • Markdown