reduce とか fold とか呼ばれる関数の一般的なイメージってこうですよね。
def reduce(f, xs):
l = len(xs)
if l == 0:
return None # or error
elif l == 1:
return xs[0] # 単要素なら確定
else:
# 単要素になるまで削って再帰
xs2 = copy.copy(xs) # (ミュータブルはめんどくさいな)
x = xs2.pop(-1)
return f(x, reduce(f, xs2))
で、これだと len(xs) == 0
のときが厄介なので、ご都合で、配列の先頭要素に一時的に入ってくれる初期値引数をオプションで持っていたりします。
def reduce(f, xs, i = None):
if i is not None:
xs2 = copy.copy(xs)
return reduce(f, xs2.insert(0, i))
l = len(xs)
# 以下上と同じ
Python の reduce はこんな感じ:
% python3
Python 3.9.2 (default, Mar 15 2021, 17:27:56)
...
>>> from functools import reduce
>>> reduce(max, [-1, -2, -3])
-1
>>> reduce(max, [-1, -2, -3], 0)
0
>>> reduce(min, [-1, -2, -3])
-3
>>> reduce(min, [-1, -2, -3], 0)
-3
>>>
Ruby の reduce (= inject):
% irb
irb(main):001:0> [-1,-2,-3].inject{|a,b| a > b ? a : b}
=> -1
irb(main):002:0> [-1,-2,-3].inject(0){|a,b| a > b ? a : b}
=> 0
irb(main):003:0> [-1,-2,-3].inject{|a,b| a < b ? a : b}
=> -3
irb(main):004:0> [-1,-2,-3].inject(0){|a,b| a < b ? a : b}
=> -3
irb(main):005:0>
(Ruby なんでもメソッドなので 2 引数の min/max 関数ないの地味にめんどい)
では本題...
「PHP は他の言語と違って reduce の初期値がつねに効いてくるから注意な」
「えっ? なにそれどういうこと??」
% php -a
Interactive shell
php > echo array_reduce([-1,-2,-3], 'max', 0);
0
php > echo array_reduce([-1,-2,-3], 'max');
-1
同じな気がするんだけどな...
php > echo array_reduce([-1,-2,-3], 'min', 0);
-3
php > echo array_reduce([-1,-2,-3], 'min');
php >
は? えっ???
array_reduce (
array $array,
callable $callback,
mixed $initial = null
) : mixed
この $initial = null
なんですが、「引数なしでコールして null のままだったら初期値を配列の先頭要素から取ってくる普通の reduce になる」って意味ではなく、本当に $callback
の初回コール初期値に null をぶっ込んできます。
というわけで種明かし。
% php -a
Interactive shell
php > var_dump(max(null, -1));
int(-1)
php > var_dump(min(null, -1));
NULL
max は null と比較したら null でない方を返すけど、min は null と比較したら null を返す関数だったので、array_reduce の初期値 null がずっと生き残っていて echo null
になったというわけです。
初回で null との比較が起きることを想定した関数なんて逆に書きにくいですね。こうすれば普通の初期値なし reduce になるのかな?
$xs = [-1,-2,-3];
echo array_reduce(array_slice($xs, 1), 'min', $xs[0] ?? null);
うーん...
echo array_reduce([-1,-2,-3], 'min', PHP_INT_MAX);
やっぱ素直にこういう「ループで書いたら初期値こうだよね」って感じのコード書くのが妥当な気がします。