[WikiDyd] [TitleIndex] [WordIndex

Języki i metodyka programowania - kurs w sem. zimowym 2010/2011

Wykład III: 19 października 2010

Mówiliśmy o czytaniu liczb (z pliku). Pierwszym problemem, który próbowaliśmy rozwiązać, była generacja danych testowych - dużego zbioru liczb losowych. Napisaliśmy w tym celu programik wykorzystujący funkcję rand() z biblioteki standardowej:

   1 #include <stdio.h> /* printf */
   2 #include <stdlib.h> /* atoi, rand */
   3 
   4 int main( int argc, char * argv[] ) {
   5    int n= argc > 1 ? atoi( argv[1] ) : 10; /* jesli byl argument - to tyle liczb piszemy, 
   6                                               jesli nie bylo - piszemy 10 liczb */
   7 
   8    int i;
   9 
  10    for( i= 0; i < n; i++ )
  11       printf( "%d\n", rand() );
  12 
  13    return 0;
  14 
  15 }

Po testach:

jstar@jstar-lap:~/jimp$ ./a.out 
1804289383
846930886
1681692777
1714636915
1957747793
424238335
719885386
1649760492
596516649
1189641421
jstar@jstar-lap:~/jimp$ ./a.out 5
1804289383
846930886
1681692777
1714636915
1957747793
jstar@jstar-lap:~/jimp$ 

widzimy, że program generuje zawsze takie same liczby całkowite - wynika to z faktu, że rand() jest generatorem pseudolosowym. Aby zainicjować generowanie różnych sekwencji można użyć zegara - biblioteka standardowa C zawiera kilka funkcji, które można wykorzystać do tego celu. Najpierw próbowaliśmy użyć f. clock(), która w teorii jest wygodna, bo zwraca czas od rozpoczęcia działania programu w milionowych (zgodnie ze standardem POSIX) częściach sekundy, ale praktyka okazała się znacznie gorsza. W końcu użyliśmy funkcji time(), zwracającej liczbę sekund, które upłyęły od 1 stycznia 1970. Dodaliśmy też możliwość określania przedziału, do którego mają należeć liczby i nauczyliśmy się zapisywać wynik działania programu do pliku:

   1 #include <stdio.h>
   2 #include <stdlib.h>
   3 #include <time.h>
   4 
   5 int
   6 main (int argc, char *argv[])
   7 {
   8   int n = argc > 1 ? atoi (argv[1]) : 10;       /* ile liczb? domyslnie 10 */
   9   double min = argc > 2 ? atof (argv[2]) : 0.0; /* minimalna możliwa wartość? domyślnie 0 */
  10   double max = argc > 3 ? atof (argv[3]) : 1.0; /* maksymalna możliwa wartość? domyślnie 1 */
  11 
  12   FILE *out = argc > 4 ? fopen (argv[4], "w") : stdout; /* do jakiego pliku pisać?
  13                                                            domyślnie na konsolę */
  14                          /* fopen otwiera plik, którego nazwę określa pierwszy argument
  15                             drugi argument mówi, w jakim celu plik otwieramy
  16                             - tutaj "w" (write) określa, że chcemy coś do niego zapisać.
  17                             fopen zwróci specjalną wartość - NULL, jeśli pliku nie uda
  18                             się otworzyć */
  19 
  20   int i;
  21 
  22   if (out == NULL) { /* sprawdzamy, czy udało się otworzyć plik podany jako argv[4] */
  23                      /* Uwaga: stdout != NULL (na pewno) */
  24     fprintf (stderr, "%s: nie moge pisac do %s\n", argv[0], argv[4]);
  25     return 1;
  26   }
  27 
  28   srand (time (NULL)); /* inicjujemy gen. losowy wartością z zegara */
  29 
  30   /* Petla drukujaca po LL liczb w jednej linii
  31      Do zmiany linii używana jest sztuczka z formatem
  32      drukujacym \n gdy i jest wielokrotnoscia LL */
  33   for (i = 0; i < n; i++) {
  34     double zerojeden = (double) rand () / RAND_MAX;
  35     fprintf (out, "%g%c",
  36              min + (max - min) * zerojeden, (i % LL == LL - 1 ? '\n' : ' ')
  37       );
  38   }
  39   if (i % LL != 0)
  40     fprintf (out, "\n");
  41 
  42   /* W zasadzie powinniśmy zamknąc plik, ale zaraz kończymy,
  43      więc i tak zrobi to za nas system. A niedobrze byłoby 
  44      zamknąć stdout !
  45 
  46      fclose( out );
  47   */
  48 
  49   return 0;
  50 }

Teraz możemy już stworzyć np. plik zawierający 5000 liczb "losowych" z zakresu od -2 do 2. Najpierw kompilujemy program tak, aby wypisywał po 5 liczb w jednej linii (definicja LL=5 w linii kompilacji):

jstar@jstar-lap:~/jimp$ cc -Wall -ansi --pedantic -DLL=5 gen.c  -o gen

Uruchamiamy:

jstar@jstar-lap:~/jimp$ ./gen 5000 -2 2 dane

Przy pomocy programu wc (word count) sprawdzamy, czy mamy tyle ile trzeba linii i liczb:

jstar@jstar-lap:~/jimp$ wc dane
 1000  5000 44760 dane
jstar@jstar-lap:~/jimp$ 

Ok - jest 1000 linii i 5000 "słów" - czyli liczb. Napiszemy teraz program, który wczyta liczby do pamięci i policzy wartość średnią - powinna być równa zero, bo rozkład liczb generowanych przez rand jest jednostajny.

Oto program:

   1 #include <stdio.h>
   2 #include <math.h>
   3 
   4 int
   5 main( int argc, char *argv[] )
   6 {
   7     double sum= 0;
   8     double sum2= 0;
   9     double x;
  10     int n= 0;
  11     while( scanf( "%lf", &x ) == 1 ) {
  12         sum += x;
  13         sum2 += x*x;
  14         n++;
  15     }
  16     printf( "n=%d, sum=%g, avg=%g std_dev=%g\n",
  17                n,      sum,    sum/n,     sqrt( n*sum2 -sum*sum )/n
  18           );
  19     return 0;
  20 }

Program ten liczy ile jest liczb, ile wynosi ich suma, ile średnia, a ile odchylenie standardowe. Skompilujemy go - w wywołaniu kompilatora pojawia się nowa opcja -lm polecająca dołączyć bibliotekę matematyczną (tam znajduje się funkcja sqrt(), której używa nasz program).

jstar@jstar-lap:~/jimp$ cc -o avg avg.c -ansi --pedantic -Wall -lm

Przetestujmy teraz jego działanie na danych o różnej wielkości:

jstar@jstar-lap:~/jimp$ ./avg < dane 
n=5000, sum=-8.69364, avg=-0.00173873 std_dev=1.15167
jstar@jstar-lap:~/jimp$ ./gen 50000 -2 2 dane
jstar@jstar-lap:~/jimp$ ./avg < dane 
n=50000, sum=-8.84771, avg=-0.000176954 std_dev=1.15375
jstar@jstar-lap:~/jimp$ ./gen 500000 -2 2 dane
jstar@jstar-lap:~/jimp$ ./avg < dane 
n=500000, sum=-410.756, avg=-0.000821513 std_dev=1.1546
jstar@jstar-lap:~/jimp$ ./gen 1000000 0 1 dane
jstar@jstar-lap:~/jimp$ ./avg < dane
n=1000000, sum=500371, avg=0.500371 std_dev=0.288501

Wygląda na to, że jest dobrze, bo średnia wartość leży w okolicach środka przedziału i zbliża się do niego ze wzrostem liczności wygenerowanych danych. Odchylenie standardowe osiąga wartość sqrt(1/12)*(max-min) - tak powinno być w przypadku rozkładu normalnego.

Na koniec program, który czyta dane z pliku i sprawdza, czy plik ten zawiera tylko liczby:

   1 #include <stdio.h>
   2 #include <math.h>
   3 
   4 int
   5 main( int argc, char * argv[] ) {
   6     double sum = 0.0;
   7     double sum2 = 0.0;
   8     double x;
   9     int n = 0;
  10 
  11     FILE *in = argc > 1 ? fopen( argv[1], "r" ) : stdin;
  12 
  13     if( in == NULL ) {
  14         fprintf( stderr, "%s: nie moge czytac %s\n", argv[0], argv[1] );
  15         return 1;
  16     }
  17 
  18     while( fscanf( in, "%lf", &x ) == 1 ) {
  19         sum += x;
  20         sum2 += x*x;
  21         n++;
  22     }
  23 
  24     if( feof( in ) ) {
  25        printf( "%d liczb, wart. srednia=%g, odch. std=%g\n",
  26                  n,                      sum/n,        sqrt( n*sum2 - sum*sum )/n
  27              );
  28        return 0;
  29     } else {
  30        fprintf( stderr, "%s: smieci w \"%s\" po przeczytaniu %i liczb\n",
  31                  argv[0], (argc > 1 ? argv[1] : "stdin"), n );
  32        return 2;
  33     }
  34 
  35 
  36     return 0;
  37 }

Byłbym zapomniał: jeszcze kilka słów o tym, jak rand() działa.


2015-09-23 06:44