Lawful Evil #3 – średnia i zliczanie elementów w tablicy

W tym odcinku opiszę problem z wątku zatytułowanego “Siema potrzebuj prosty program w c++ w visual studio ktoś potrafi ?”:

Napisz program, który zapisuje do tablicy 10 elementowej liczby rzeczywiste
dodatnie, w momencie, gdy podana przez użytkownika liczba jest ujemna program
wyświetla informację o błędnej liczbie i prosi o podanie innej liczby.
Po zakończeniu wprowadzania program powinien wywołać funkcję, która znajdzie
średnią wartość tablicy i policzy ile elementów tablicy jest większych od średniej.
Po zakończeniu działania funkcji w programie głównym mają zostać zapisane w
zmiennych wartość średnia oraz ilość liczb większych od średniej.

Pierwszą częścią zadania jest uzupełnienie tablicy. Do tego przygotowałem rekurencyjny szablon funkcji read. Przyjmuje on wskaźnik na pierwszy element tablicy, liczbę elementów pozostałych do wczytania oraz obiekt wczytujący, nazwany tutaj Getter:

template<typename T, typename Getter>
void read(T* ptr, size_t n, Getter&& g){
	if(!n) return;
	g(*ptr);
	read(++ptr, --n, forward<Getter>(g));
}

Użycie tego na większych tablicach jest pięknie widoczne na callstacku.

Dla ułatwienia użycia z tablicą dodałem też szablon funkcji dedukujący jej wielkość:

template<typename T, size_t N, typename Getter>
void read(T(&arr)[N], Getter&& g){
	read(arr, N, forward<Getter>(g));
}

Mając gotowy framework do wczytywania, przystąpiłem do implementacji klasy wczytującej liczby dodatnie:

template<typename T>
struct PositiveGetter
{
	void operator()(T& ref) const{
		ref = -1;
		cout << "Podaj liczbe: " << endl;
		cin >> ref;
		while(!(cin) || ref < 0){
			cout << "Podaj jeszcze raz: " << endl;
			cin.clear();
			cin.ignore(numeric_limits<streamsize>::max(), '\n');
			cin >> ref;
		}
	}
};

Jak widać jest to wersja uproszczona, mająca zahardkodowany nie tylko typ wejścia, ale także konkretny obiekt. Dlatego też dodałem dodatkowy poziom abstrakcji: fabrykę getterów, również uproszczoną.

template<typename T>
struct PositiveGetterFactory
{
	PositiveGetter<T> operator()() const {
		return PositiveGetter<T>();
	}
};

Okej, wczytywanie jest już w pełni zrobione. Teraz pora na drugą część zadania – obliczenia. Do tego utworzyłem prosty szablon funkcji getAverageAndAboveAveragesCount zwracający średnią i liczbę elementów ponad średnią:

template<typename It>
pair<double, size_t> getAverageAndAboveAveragesCount(It b, It e){
	auto sum = accumulate(b, e, 0.0, plus<double>{});
	auto avg = sum/distance(b, e);
 
	auto above = count_if(b, e, [&](double val){ return val > avg;});
	return make_pair(avg, above);
}

Akurat to jest kawałek, którego zupełnie serio nie powstydziłbym się w kodzie produkcyjnym. Jest odpowiednio generyczny i raczej nie da się go specjalnie przyspieszyć. Ewentualnie można:

  • wykryć kontener asocjacyjny i sprawdzić, czy member function lower_bound nie pozwoli na szybsze obliczenie liczby wartości powyżej średniej,
  • zamienić inicjalizację wyniku 0.0 na zależną od typu wprowadzanego, na przykład std::decay_t<decltype(*b)>{}, aby nie wymuszać typu wynikowego, co może być przydatne przy użyciu różnych bigintów. Chociaż wtedy trzeba by było się zastanowić co zrobić dla liczb całkowitych, których średnia straci na precyzji…

W każdym razie, w tym momencie nie zostało nic innego niż wszystko to zebrać do kupy, dodać wszystkie niezbędne include‘y i sprawdzić w użyciu:

#include <cassert>
#include <climits>
#include <cstdlib>
#include <cstring>
 
#include <algorithm>
#include <array>
#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <thread>
#include <typeinfo>
#include <tuple>
#include <unordered_set>
 
 
using namespace std;
 
constexpr int Width = 60;
#define DBG(x) { cout << left << setw(Width) << #x << boolalpha << (x) << endl; }
#define DBG_CONT(x) { cout << left << setw(Width) << #x; for(auto const& _name : (x)) \
	cout << boolalpha << _name << " "; cout << endl; }
 
template<typename T, typename Getter>
void read(T* ptr, size_t n, Getter&& g){
	if(!n) return;
	g(*ptr);
	read(++ptr, --n, forward<Getter>(g));
}
 
template<typename T, size_t N, typename Getter>
void read(T(&arr)[N], Getter&& g){
	read(arr, N, forward<Getter>(g));
}
 
template<typename T>
struct PositiveGetter
{
	void operator()(T& ref) const{
		ref = -1;
		cout << "Podaj liczbe: " << endl;
		cin >> ref;
		while(!(cin) || ref < 0){
			cout << "Podaj jeszcze raz: " << endl;
			cin.clear();
			cin.ignore(numeric_limits<streamsize>::max(), '\n');
			cin >> ref;
		}
	}
};
 
template<typename T>
struct PositiveGetterFactory
{
	PositiveGetter<T> operator()() const {
		return PositiveGetter<T>();
	}
};
 
template<typename It>
pair<double, size_t> getAverageAndAboveAveragesCount(It b, It e){
	auto sum = accumulate(b, e, 0.0, plus<double>{});
	auto avg = sum/distance(b, e);
 
	auto above = count_if(b, e, [&](double val){ return val > avg;});
	return make_pair(avg, above);
}
 
int main()
{
	double tab[10];
 
	auto factory = PositiveGetterFactory<double>();
	auto getter = factory();
 
	read(tab, getter);
 
	auto computed = getAverageAndAboveAveragesCount(begin(tab), end(tab));
 
	cout << "srednia: " << computed.first << endl;
	cout << "wiekszych niz srednia: " << computed.second << endl;
 
}

Tym razem wygrało dobro (kq):

To jest język rozszerzony
~ Autor wątku

One thought on “Lawful Evil #3 – średnia i zliczanie elementów w tablicy

Leave a Reply

Your email address will not be published.