Esercizio - Predicati e algoritmi STL (primo appello 2022-23, teoria)
Domanda aperta 3 del primo appello 2022-23 del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. Teoria: STL - algoritmi, predicati e lambdaLa STL ha circa 80 algoritmi generici (header
Testo. Illustrare il concetto di predicato e fornire un esempio in C++. Spiegare in che modo un predicato può essere usato in un algoritmo STL, corredando la spiegazione con un esempio di codice.
Il predicato
Predicato. Una funzione (o un oggetto chiamabile) che restituisce
trueofalsee che esprime un criterio su un elemento (o su due, nel caso dei criteri di ordinamento). Esempio:bool odd(int x) { return x % 2; }(dispari). Gli algoritmi STL generici lo ricevono come argomento: il cosa (la ricerca) è nell'algoritmo, il criterio nel predicato.
Un predicato può essere espresso in tre modi:
| modo | codice | quando |
|---|---|---|
| puntatore a funzione | bool odd(int x); si passa il nome, senza () (il nome di una funzione senza parentesi è il puntatore a quella funzione) |
criterio senza parametri |
| function object | classe con operator(): class Larger_than { int v; public: Larger_than(int vv) : v{vv} {} bool operator()(int x) const { return x > v; } }; |
criterio con parametri (la soglia viaggia nel dato membro): niente variabili globali |
| lambda expression | [soglia](int x) { return x > soglia; } |
criterio usato in un punto solo, scritto nel punto d'uso; cattura le variabili locali ([=] per valore, [&] per riferimento) |
Perché non una variabile globale per la soglia? Funziona, ma è modificabile ovunque, non c'è incapsulamento, il codice è fragile: il function object e la lambda risolvono il problema.
Uso in un algoritmo STL
Gli algoritmi con predicato sono, tra gli altri: find_if(b, e, p) (primo elemento per cui p(x) è vero, oppure e), count_if(b, e, p) (quanti), copy_if(b, e, b2, p) (copia quelli per cui è vero), sort(b, e, cmp) (ordina secondo cmp(a, b): "a viene prima di b"). find_if internamente fa
while (first != last && !pred(*first)) ++first; // pred(*first) e' una chiamata a funzione o a operator()
return first;Il predicato è passato per copia (un function object piccolo è copiato: va bene; se grande, per riferimento) e find_if lo chiama con ogni elemento. Funziona con qualsiasi container (vector, list) e tipo di elemento, perché l'interfaccia è fatta di iteratori.
#include <algorithm>
#include <iostream>
#include <list>
#include <string>
#include <vector>
using namespace std;
// PREDICATO: una funzione che restituisce true o false
bool odd(int x) { return x % 2; }
// Predicato con un parametro (la soglia): function object, cioe' una classe con operator()
class Larger_than {
public:
explicit Larger_than(int vv) : v{vv} {} // salva la soglia nel dato membro
bool operator()(int x) const { return x > v; } // il predicato vero e proprio
private:
int v;
};
int main()
{
vector<int> v {4, 8, 15, 16, 23, 42};
list<double> l {1.5, 31.0, 40.5, 12.25};
// 1) puntatore a funzione: si passa il NOME della funzione, senza ()
auto p = find_if(v.begin(), v.end(), odd);
cout << "primo dispari: " << (p != v.end() ? to_string(*p) : "nessuno") << '\n';
// 2) function object: la soglia viaggia nell'oggetto
auto q = find_if(l.begin(), l.end(), Larger_than(31));
cout << "primo > 31: " << (q != l.end() ? to_string(*q) : "nessuno") << '\n';
// 3) lambda: il predicato e' scritto nel punto d'uso e cattura la soglia
int soglia = 15;
auto r = find_if(v.begin(), v.end(), [soglia](int x) { return x > soglia; });
cout << "primo > " << soglia << ": " << *r << '\n';
// lo stesso predicato in altri algoritmi
cout << "quanti dispari: " << count_if(v.begin(), v.end(), odd) << '\n';
vector<int> grandi(v.size());
auto fine = copy_if(v.begin(), v.end(), grandi.begin(), Larger_than(15)); // copia solo quelli per cui e' vero
grandi.erase(fine, grandi.end());
cout << "copiati > 15:";
for (int x : grandi) cout << ' ' << x;
cout << '\n';
return 0;
}primo dispari: 15
primo > 31: 40.500000
primo > 15: 16
quanti dispari: 2
copiati > 15: 16 23 42find_if(v.begin(), v.end(), odd)primo dispari di{4, 8, 15, 16, 23, 42}:15.find_if(l.begin(), l.end(), Larger_than(31))su unalist<double>40.5(31.0non è maggiore di 31): lo stesso algoritmo su un altro container, con un function object.find_ifcon la lambda[soglia](int x) { return x > soglia; }16.count_if(..., odd)2 (15 e 23);copy_ifconLarger_than(15)16 23 42;grandi.erase(fine, grandi.end())toglie le posizioni non scritte.- Efficienza: un function object piccolo con
operator()inline può essere più efficiente di una chiamata a funzione.
Versione ripasso
- Predicato = funzione/oggetto che restituisce
true/falsee rappresenta un criterio:bool odd(int x) { return x % 2; }. L'algoritmo STL ha il cosa, il predicato il criterio. Rimando: STL - algoritmi, predicati e lambdaLa STL ha circa 80 algoritmi generici (headere .) che lavorano su sequenze [first, last) di qualunque contenitore tramite iteratori: find/find_if, count/count_if, sort, copy/copy_if/unique_copy, merge, equal_range, equal, accumulate, inner_product. find cerca un valore e restituisce l'iteratore alla prima occorrenza o end(); find_if verifica un predicato (funzione che restituisce bool) passato come puntatore a funzione, function object (classe con operator()) o lambda expression. Una variabile globale per passare la soglia al predicato è da evitare; il function object la memorizza in un dato membro. Una lambda cattura -> tipo { corpo } definisce la funzione sul posto e può catturare le variabili locali per valore ([=], [x]) o per riferimento ([&], [&x]). sort(b, e) ordina con < e sort(b, e, cmp) con un criterio, solo su iteratori random access; copy richiede spazio a destinazione.STL - algoritmi, predicati e lambda → - Tre forme: (1) puntatore a funzione: si passa il nome senza
(); (2) function objectclass Larger_than { int v; public: Larger_than(int vv) : v{vv} {} bool operator()(int x) const { return x > v; } };(soglia nel dato membro; niente globali); (3) lambda[soglia](int x) { return x > soglia; }(cattura:[=]valore,[&]riferimento). - Algoritmi:
find_if(b, e, p)(primo conpvero, altrimentie),count_if,copy_if(b, e, b2, p),sort(b, e, cmp); dentrofind_if:while (first != last && !pred(*first)) ++first;. Il predicato è passato per copia; funziona con qualunque container e tipo. - Esempio:
v = {4, 8, 15, 16, 23, 42}:find_if(..., odd)15;Larger_than(31)sulist<double>{1.5, 31.0, 40.5, 12.25}40.5; lambda consoglia = 1516;count_if(..., odd)2;copy_if(..., Larger_than(15))16 23 42. - Errori tipici: scrivere
odd()con le parentesi (chiamata, non nome); soglia in una variabile globale;operator()nonconst; lambda con[=]pensando che catturi per riferimento;sortcon un predicato non "minore" stretto.