LoginSignup
0
0

More than 3 years have passed since last update.

PHP は初期値なし reduce できない

Posted at

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);

やっぱ素直にこういう「ループで書いたら初期値こうだよね」って感じのコード書くのが妥当な気がします。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0