数式をこねくりまわす知識や技術はないので自分で求められないのですが、平均値って逐次計算できるのかな?を調べたメモ。
x = [...] // 元の数列
z = ... // 追加したい数
// 平均値
Average(x) = Sum(x) / Count(x)
// 数を追加した平均値
Average(x + z) = Average(x) + ((z - Average(x)) / Count(x))
調べていたらこういう式も。平均に個数をかけて数を足し、そこから新しい個数で割れば、新しい平均だよね、というもの。そりゃそうだわ。
Average(x + z) = (Average(x) * Count(x) + z) / Count(x + z)
どちらにしても個数と平均値だけ持っておけば逐次計算できますね。後者は掛け算するので、個数や平均値が大きすぎると扱えなさそうなので、前者のほうがよさそう。
試しに実装してみるとこういう形。
class MeanStream
{
private $mean = 0;
private $dataCount = 0;
public function __construct() {
}
public function add($num) {
$this->dataCount++;
$this->mean = $this->mean + (($num - $this->mean) / $this->dataCount);
return $this;
}
public function get() {
return $this->mean;
}
}
echo new MeanStream()
->add(1)
->add(7)
->add(8)
->add(6)
->add(2)
->add(5)
->add(1)
->add(8)
->add(9)
->add(4)
->get();
echo (1+7+8+6+2+5+1+8+9+4) / 10;
// 5.1
あってますねー
ちなみに、当然のことながら上記の例だと浮動小数点数を使うので、使う値によっては表現の都合で微妙に数値が変わるのでその点だけ注意ですね。
$values = [];
for($i = 0; $i < 10; $i++) {
$values[] = mt_rand();
}
$mean = new MeanStream();
foreach ($values as $value) {
$mean->add($value);
}
echo sprintf('%.30f', array_sum($values) / count($values));
// 995988838.500000000000000000000000000000
echo sprintf('%.30f', $mean->get());
// 995988838.499999880790710449218750000000
PHP じゃない言語で実装するとして、もし Decimal のような、よろしく精度をもってくれる機能があればそれを使ってあげるほうがよさそうです。PHP は言語として標準に Decimal がないのでどうするんだろう、 bcmath とか gmp というのがありそうだけど extension に左右されるなー。まあ composer.json で extension について依存あるよーと記載すればいいのか。 PHP: 数学 - Manual
Packagist みると幾つかありそうなので、このあたりを使ったほうがいいのかなー。
と、話がそれてきたあたりでおしまい。ライブラリの話はまた今度。