2015-04-16 6 views
13

Ho un contenitore di libreria standard di grandi numeri, così grande da poter causare un overflow se li aggiungo insieme. Facciamo finta che sia questo contenitore:Come posso usare std :: accumulate e un lambda per calcolare una media?

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 

voglio calcolare la media di questo contenitore, utilizzando std :: si accumulano, ma non posso aggiungere tutti i numeri insieme. Lo calcolerò semplicemente con v[0]/v.size() + v[1]/v.size() + .... Così mi sono messo:

auto lambda = ...; 
std::cout << std::accumulate(v.begin(), v.end(), 0, lambda) << std::endl; 

Ecco quello che ho provato finora, dove -> indica l'uscita:

lambda = [&](int a, int b){return (a + b)/v.size();}; -> 1 
lambda = [&](int a, int b){return a/v.size() + b/v.size();}; -> 1 
lambda = [&](int a, int b){return a/v.size() + b;}; -> 10 

Come posso produrre la corretta media in modo tale che l'uscita sarà 5?

+2

'5' non è la risposta corretta. –

+0

@BenVoigt È se si utilizza la divisione integer. – EMBLEM

+2

La divisione intera non viene utilizzata nel calcolo di una media. Combinato con 'std :: accumulate', è anche peggio - rovinerà le tue somme parziali. Se vuoi che il risultato finale sia arrotondato secondo le regole della divisione intera, dovresti dire esplicitamente nella tua domanda (e quindi non stai trovando una media). Altrimenti l'uso della divisione intera sembra un bug per ogni lettore. –

risposta

14

Non si deve usare intero per memorizzare il risultato:

Il tipo restituito passato alla funzione accumulate:
T accumulate(InputIt first, InputIt last, T init, BinaryOperation op); dipende dal terzo tipo di parametro: (T init) in modo da avere mettere lì: 0.0 per ottenere risultato come doppio.

#include <vector> 
#include <algorithm> 
#include <iostream> 
#include <numeric> 
using namespace std; 
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 

int main() 
{ 
    auto lambda = [&](double a, double b){return a + b/v.size(); }; 
    std::cout << std::accumulate(v.begin(), v.end(), 0.0, lambda) << std::endl; 
} 
+0

@rpattabi Potresti elaborare un po 'di più come riprodurre l'avviso senza tipo di ritorno? Non sono in grado di farlo. – AdamF

0

La "media" in esecuzione è il primo parametro per lambda, quindi quanto segue è corretto.

lambda = [&](int a, int b){return a + b/v.size();}; 
+3

non ci sarà un problema con l'arrotondamento dei numeri interi? – dwcanillas

6

Questo non può arrotondare altrettanto bene, ma funziona anche quando non c'è nessun metodo size() sul contenitore:

auto lambda = [count = 0](double a, int b) mutable { return a + (b-a)/++count; }; 

Questo si avvale di un nuovo C++ 14 caratteristiche, Cattura inizializzati, per memorizzare lo stato all'interno della lambda. (Puoi fare la stessa cosa catturando una variabile locale extra, ma il suo ambito è l'ambito locale, piuttosto che il lifetime del lambda.) Per le versioni C++ più vecchie, puoi semplicemente inserire lo count nella variabile membro di un struct e inserire il corpo lambda come implementazione operator()().

per evitare l'accumulo di errore di arrotondamento (o almeno ridurre drasticamente esso), si può fare qualcosa di simile:

auto lambda = [count = 0, error = 0.0](double a, int b) mutable { 
    const double desired_change = (b-a-error)/++count; 
    const double newa = a + (desired_change + error); 
    const double actual_change = newa - a; 
    error += desired_change - actual_change; 
    return newa; 
}; 
+0

È utile utilizzare questa formula di approssimazione per un set di dati di grandi dimensioni, in quanto la qualità del doppio potrebbe non essere sufficiente nella formula originale. – AdamF

+0

@AdamF: si può anche tenere traccia di un termine di errore, per evitare che si accumulino errori di arrotondamento. –

+0

@AdamF: Meglio? –

Problemi correlati