4Тестване на функции, вектори

    Самостоятелно тестване на функции. 
** Данните, с които ще се тества функцията, се получават по 3 начина: 
-- от входния поток; 
-- като стойности, получени от цикъл; 
-- случайни числа. 
** Примери за тестване на функцията squareroot за намиране на квадратен корен по метода на Херон.
* Първи пример - данните идват от входния поток: 
// sqrtest1.cpp 
#include <iostream>
#include <cmath>
using namespace std;

/**
   Tests whether two floating-point numbers are 
   approximately equal.
   @param x a floating-point number
   @param y another floating-point number
   @return true if x and y are approximately equal
*/
bool approx_equal(double x, double y)
{  const double EPSILON = 1E-14;
   if (x == 0) return fabs(y) <= EPSILON;
   if (y == 0) return fabs(x) <= EPSILON;
   return fabs(x - y) / max(fabs(x), fabs(y)) <= EPSILON;
}

/* Function to be tested */
/**
   Computes the square root using Heron's formula
   @param a an integer >= 0
   @return the square root of a
*/
double squareroot(double a)
{  if (a == 0) return 0;

   double xnew = a;
   double xold;
   do
   {  xold = xnew;
      xnew = (xold + a / xold) / 2;
   }
   while (!approx_equal(xnew, xold));
   return xnew;
}

/* Test harness */
int main()
{  double x;
   while (cin >> x)
   {  double y = squareroot(x);
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
25 
squareroot of 25 = 5 
3 
squareroot of 3 = 1.73205 
q

* Втори пример - входните стойности на функцията се генерират от цикъл. 
// sqrtest2.cpp 

/* Test harness */
int main()
{  double x;
   for (x = 0; x <= 10; x = x + 0.5)
   {  double y = squareroot(x);
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
squareroot of 0 = 0 
squareroot of 0.5 = 0.707107 
squareroot of 1 = 1 
squareroot of 1.5 = 1.22474 
squareroot of 2 = 1.41421

* Трети пример - входните стойности се получават от генератор за случайни числа. 
// sqrtest3.cpp 

#include <iostream>
#include <cstdlib>
#include <cmath>
#include <ctime>
using namespace std;

/**
   Sets the seed of the random number generator.
*/
void rand_seed()
{  int seed = static_cast<int>(time(0));
   srand(seed);
}

/** 
   Compute a random floating point number in a range
   @param a the bottom of the range
   @param b the top of the range
   @return a random floating point number x, 
   a <= x and x <= b
*/
double rand_double(double a, double b)
{  return a + (b - a) * rand() * (1.0 / RAND_MAX);
}

/**
   Tests whether two floating-point numbers are 
   approximately equal.
   @param x a floating-point number
   @param y another floating-point number
   @return true if x and y are approximately equal
*/
bool approx_equal(double x, double y)
{  const double EPSILON = 1E-14;
   if (x == 0) return fabs(y) <= EPSILON;
   if (y == 0) return fabs(x) <= EPSILON;
   return fabs(x - y) / max(fabs(x), fabs(y)) <= EPSILON;
}

/* Function to be tested */
/**
   Computes the square root using Heron's formula
   @param a an integer >= 0
   @return the square root of a
*/
double squareroot(double a)
{  if (a == 0) return 0;

   double xnew = a;
   double xold;
   do
   {  xold = xnew;
      xnew = (xold + a / xold) / 2;
   }
   while (!approx_equal(xnew, xold));
   return xnew;
}

/* Test harness */
int main()
{  rand_seed();
   int i;
   for (i = 1; i <= 100; i++)
   {  double x = rand_double(0, 1E6);
      double y = squareroot(x);
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
squareroot of 185949 = 431.218 
squareroot of 680715 = 825.055 
squareroot of 17883.8 = 133.73 
squareroot of 238868 = 488.742

 Подбор на тестови примери. 
1. Докато се пише програмата, трябва да имаме прост тестов пример, на който знаем решението. 
2. Програмата се проверява с други тестови примери, също с известни решения - позитивни тестове. 
3. Включват се и граничните случаи. Например за функцията squareroot това са 0, големи числа (напр. 1Е20) и числа, близки до 0 (напр.1е-20). Целта е да се определят границите на параметрите, за които функцията работи вярно. 
4. Функцията се проверява с негативни тестови примери - некоректни входни данни. Такива за squareroot са отрицателни стойности на параметъра. 



 Оценка на резултатите от тестването. 
** Как можем да оценим дали върнатата стойност от функцията е вярна: 
- като предварително знаем решението; 
- с програма за проверка на върнатите стойности; 
- с "оракул" - друга функция, която решава същата задача (може по-бавно и неефективно!). 
** В нашия случай можем да сравним квадрата на върнатото число от функцията с входната стойност. Ето тестващото гнездо: 
// sqrtest4.cpp
/* Test harness */
int main()
{  int i;
   for (i = 1; i <= 100; i++)
   {  double x = rand_double(0, 1E6);
      double y = squareroot(x);
      if (not_equal(y * y, x)) 
         cout << "Test failed. ";
      else 
         cout << "Test passed. ";
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
**  За оракул можем да използваме стандартната аритметична функция pow(x,0.5)или функцията sqrt(x). 
// sqrtest5.cpp
/* Тестващо гнездо */
 
int main() 
{ 
   rand_seed(); 
   int i; 
   for (i = 1; i <= 100; i++) 
   {  double x = rand_double(0, 1E6); 
      double y = squareroot(x); 
      if (not_equal(y, pow(x, 0.5))) cout << "Test failed. "; 
      else                           cout << "Test passed. "; 
      cout << "squareroot of " << x << " = " << y << "\n"; 
   } 
   return 0; 
}


 Използване на вектори за съхраняване на данни.
* Векторът е множество от данни от един и същи тип и данните са номерирани с последователни неотрицателни цели числа.
    Дефиниция на вектор с 10 елемента тип double:
  vector<double> salaries(10);
На елемента с индекс 4 се задава стойност 355.
  salaries[4] = 355;
Елементите на вектора се номерират, започвайки от 0.
  salaries[0] - първи елемент
  salaries[1] - втори елемент
  salaries[2] - трети елемент
  salaries[3] - четвърти елемент
  ...
  salaries[9] - десети елемент


Индекси на вектори.
* Граници на индексите са от 0 до размерността на вектора минус 1. Грешка  "излизане извън границите на вектора" (или грешка при индексиране) се получава, когато индексираме елемент на вектор с число извън интервала [0, размерност-1]. Тази грешка не се открива нито от компилатора, нито от операционната система по време на изпълнение на програмата. Обикновено тя довежда до повреда на някакъв участък от паметта. Това е една от възможните грешки в една програма, която се открива много трудно.
* Член-функцията за получаване на размерността на вектора е size(). Ето типичен цикъл за обхождане на елементите на вектора (в случая за отпечатването им) с използване на тази член-функция.
  for (i=0; i < salaries.size(); i++) cout << salaries[i];
* Може да се дефинира и вектор с 0 елементи:
  vector<double> salaries;
* Типът vector е реализиран като клас в който са дефинирани член-функциите push_back и pop_back за увеличаване и намаляване на размера на вектора.
  salaries.push_back(елемент)- добавяне на елемент към вектора (след последния);
  salaries.pop_back()- премахване на  последния елемент на вектора.
* Пример. Четат се заплати на служители, стойностите се записват във вектор и се печати отбелязка на най-високата заплата:
 // salvect.cpp
#include
<iostream>
#include <vector> 
using namespace std;
 
int main()
{  vector<double> salaries;
    bool more = true;
    while (more)
    {  double s;
       cout << "Please enter a salary, 0 to quit: ";
       cin >> s;
       if (s == 0) more = false;
       else salaries.push_back(s);
    } 
    double highest = salaries[0];
    int i;
    for (i = 1; i < salaries.size(); i++)
       if (salaries[i] > highest)
          highest = salaries[i];
 
    for (i = 0; i < salaries.size(); i++)
    {  if (salaries[i] == highest) 
          cout << "highest value => ";
       cout << salaries[i] << "\n";
    } 
    return 0;
}


Векторите като параметри и стойности на функции.
* Намиране на средно аритметично на елементите на вектор.
// average.cpp
#include <iostream>
#include <vector>
using namespace std;
 

/* v е параметър-променлива */
double average(vector<double> v)
{  if (v.size() == 0) return 0;
   int i;
   double sum = 0;
   for (i = 0; i < v.size(); i++)  sum = sum + v[i];
   return sum / v.size();
}
int main()
{  vector<double> salaries(5);
   salaries[0] = 35000.0;
   salaries[1] = 63000.0;
   salaries[2] = 48000.0;
   salaries[3] = 78000.0;
   salaries[4] = 51500.0;

   double avgsal = average(salaries);
   cout << "The average salary is " << avgsal << "\n";
   return 0;
}

The average salary is 55100

*  Промяна на стойностите на елементите на вектор. В примера заплатите се увеличават с 4.5 процента. Използва се параметър-псевдоним.
// raisesal.cpp
#include <iostream>
#include <vector>
using namespace std;

/* s е параметър-псевдоним */
void raise_salaries(vector<double>& s, double p)
{  int i;
   for (i=0; i<s.size(); i++) s[i] = s[i]*(1 + p/100);
}
int main()
{  vector<double> salaries(5);
   salaries[0] = 35000.0;
   salaries[1] = 63000.0;
   salaries[2] = 48000.0;
   salaries[3] = 78000.0;
   salaries[4] = 51500.0;
   raise_salaries(salaries, 4.5);
   int i;
   for (i = 0; i < salaries.size(); i++)
            cout << salaries[i] << "\n";
   return 0;
}

36575
65835
50160
81510
53817.5

* Вектор като връщана стойност на функция. Намиране на всички стойности, които попадат в определен интервал. Функцията връща стойност от тип vector<double>.
// between.cpp
#include <iostream>
#include <vector>
using namespace std;

/* връщаната стойност на функцията е от тип vector */
vector<double>
        between(vector<double> v, double low, double high)
{ vector<double> result;
  int i;
  for (i = 0; i < v.size(); i++)
  if (low <= v[i] and v[i] <= high) result.push_back(v[i]);
  return result;
}
int main()
{  vector<double> salaries(5);
   salaries[0] = 35000.0;
   salaries[1] = 63000.0;
   salaries[2] = 48000.0;
   salaries[3] = 78000.0;
   salaries[4] = 51500.0;
   vector<double> midrange_salaries
                  = between(salaries, 45000.0, 65000.0);
   int i;
   for (i = 0; i < midrange_salaries.size(); i++)
                   cout << midrange_salaries[i] << "\n";
   return 0;
}
 

63000
48000
51500

Прости алгоритми за вектори.
* Намиране на всички съответствия. Функцията връща вектор, съдържащ индексите на всички елементи, които удовлетворяват дадено условие.
// matches.cpp
#include <iostream>
#include <vector>
using namespace std;

vector<int> find_all_between(vector<double> v, 
                        double low, double high)

/* ЦЕЛ:      намира елементите на вектора a
             със стойност по-голяма от low и по-малка от high
   ПОЛУЧАВА: вектор a и число t
   ВРЪЩА:    вектор с индексите на намерените елементи,
             по-големи от t
*/

{  vector<int> pos;
    for (int i = 0; i < v.size(); i++)
      if (low <= v[i] && v[i] <= high)
          pos.push_back(i);
    
    return pos;
}

int main()
{  vector<double> salaries(5);
    salaries[0] = 35000.0;
    salaries[1] = 63000.0;
    salaries[2] = 48000.0;
    salaries[3] = 78000.0;
    salaries[4] = 51500.0;
 
    vector<int> matches
       = find_all_between(salaries, 45000.0, 65000.0);
 
    for (int j = 0; j < matches.size(); j++)
       cout << salaries[matches[j]] << "\n";
    return 0;
}

63000
51500

* Изтриване на елемент от вектор. Целта е да се изтрие един елемент на вектора и всички елементи след него да се преместят с една позиция назад - т.е. индексите на елементите след него да се намалят с 1.

// erase2.cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;

void erase(vector<string>& a, int pos)
/* ЦЕЛ: отстранява елемент с индекс pos от вектор a
   ПОЛУЧАВА: вектор a и индекс pos
   ЗАБЕЛЕЖКА: редът на оставащите елементи се запазва
*/
{  int i;
   for (i = pos; i < a.size()-1; i++) a[i] = a[i+1];
   a.pop_back();
}
void print(vector<string> a)
{  int i;
   for (i = 0; i < a.size(); i++)
      cout << "[" << i << "] " << a[i] << "\n";
}
int main()
{  vector<string> staff(5);
   staff[0] = "Cracker, Carl";
   staff[1] = "Hacker, Harry";
   staff[2] = "Lam, Larry";
   staff[3] = "Reindeer, Rudolf";
   staff[4] = "Sandman, Susan";
   print(staff);
   int pos;
   cout << "Remove which element? ";
   cin >> pos;
   erase(staff, pos);
   print(staff);
   return 0;
}

[0] Cracker, Carl
[1] Hacker, Harry
[2] Lam, Larry
[3] Reindeer, Rudolf
[4] Sandman, Susan
Remove which element? 2
[0] Cracker, Carl
[1] Hacker, Harry
[2] Reindeer, Rudolf
[3] Sandman, Susan

* Вмъкване на елемент на вектор. Целта е да се вмъкне нов елемент на вектора на зададена позиция и всички елементи след него да се преместят с една позиция напред - т.е. индексите на елементите след новия елемент да се увеличат с 1.
// insert.cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;

void insert(vector<string>& a, int pos, string s)
/* ЦЕЛ: вмъква елемент s с индекс pos във вектора a
   ПОЛУЧАВА: вектор a, индекс pos и елемент s
   ЗАБЕЛЕЖКА: редът на останалите елементи се запазва
*/
{  int last = a.size() - 1;
   a.push_back(a[last]);
   int i;
   for (i = last; i > pos; i--) a[i] = a[i - 1];
   a[pos] = s;
}
void print(vector<string> a)
{  int i;
   for (i = 0; i < a.size(); i++)
      cout << "[" << i << "] " << a[i] << "\n";
}
int main()
{  vector<string> staff(5);
   staff[0] = "Cracker, Carl";
   staff[1] = "Hacker, Harry";
   staff[2] = "Lam, Larry";
   staff[3] = "Reindeer, Rudolf";
   staff[4] = "Sandman, Susan";
   print(staff);
   int pos;
   cout << "Insert before which element? ";
   cin >> pos;
   insert(staff, pos, "New, Nina");
   print(staff);
   return 0;
}
 

[0] Cracker, Carl
[1] Hacker, Harry
[2] Lam, Larry
[3] Reindeer, Rudolf
[4] Sandman, Susan
Insert before which element? 1
[0] Cracker, Carl
[1] New, Nina
[2] Hacker, Harry
[3] Lam, Larry
[4] Reindeer, Rudolf
[5] Sandman, Susan

Успоредни вектори и вектор от обекти.
*  Два вектора са успоредни, ако съществува логическа (смислова) връзка между елементите им с еднакви индекси.
*  Задачата за най-добро отношение цена-качество на компютри.

 // bestval1.cpp
#include
<iostream>
#include <string>
#include <vector> 
using namespace std;
 
int main()
{  vector<string> names;
    vector<double> prices;
    vector<int> scores;
 
    double best_price = 1;
    int best_score = 0;
    int best_index = -1;
 
    bool more = true;
    while (more)
    {  string next_name;
       cout << "Please enter the model name: ";
       getline(cin, next_name);
       names.push_back(next_name);
       double next_price;
       cout << "Please enter the price: ";
       cin >> next_price;
       prices.push_back(next_price);
       int next_score;
       cout << "Please enter the score: ";
       cin >> next_score;
       scores.push_back(next_score);
       string remainder; /* read remainder of line */
       getline(cin, remainder); 
 
       if (next_score / next_price > best_score / best_price)
       {  best_index = names.size() - 1;
          best_score = next_score;
          best_price = next_price;
       }      
       cout << "More data? (y/n) ";
       string answer;
       getline(cin, answer);
       if (answer != "y") more = false;
    } 
    for (int i = 0; i < names.size(); i++)
    {  if (i == best_index) cout << "best value => ";
       cout << names[i]
            << " Price: " << prices[i]
            << " Score: " << scores[i] << "\n";
    } 
    return 0;
}

* Успоредните вектори обикновено създават проблеми и трябва да се избягват. По-добро решение на задачата е с използване на вектор от обекти.

 // bestval2.cpp
#include
<iostream>
#include <string>
#include <vector> 
using namespace std;
 
class Product {
public:
    Product();
    void read();
    bool is_better_than(Product b) const;
    void print() const;
private:
    string name;
    double price;
    int score;
};
 
Product::Product()
{  price = 0;
    score = 0;
}
 
void Product::read()
{  cout << "Please enter the model name: ";
    getline(cin, name);
    cout << "Please enter the price: ";
    cin >> price;
    cout << "Please enter the score: ";
    cin >> score;
    string remainder; /* read remainder of line */
    getline(cin, remainder);
}
 
bool Product::is_better_than(Product b) const
{  if (price == 0) return false;
    if (b.price == 0) return true;
    return score / price > b.score / b.price;
}
 
void Product::print() const
{  cout << name
         << " Price: " << price
         << " Score: " << score << "\n";
}
 
int main()
{  vector<Product> products;
    Product best_product;
    int best_index = -1;
 
    bool more = true;
    while (more)
    {  Product next_product;
       next_product.read();
       products.push_back(next_product);
 
       if (next_product.is_better_than(best_product))
       {  best_index = products.size() - 1;
          best_product = next_product;
       }     
 
       cout << "More data? (y/n) ";
       string answer;
       getline(cin, answer);
       if (answer != "y") more = false;
    }
 
    for (int i = 0; i < products.size(); i++)
    {  if (i == best_index) cout << "best value => ";
       products[i].print();
    } 
    return 0;
}