Как выйти из итерационных функций массива (array_reduce) в PHP

У меня есть функция array_reduce, которую я готов выйти, когда будут выполнены определенные критерии.

$result = array_reduce($input, function($carrier, $item) {
  // do the $carrier stuff
  if (/* god was one of us */) {
    break; //some break analogue
  }
  return $carrier;
});

Как мне этого добиться? Или я должен использовать foreach вместо этого?

5 ответов

array_reduce используется для написания кода функционального стиля, который всегда перебирает весь массив. Вы можете либо переписать, чтобы использовать обычный цикл foreach для реализации логики короткого замыкания, либо просто вернуть текущий $carrier неизмененной. Это все равно будет повторяться по вашему полному массиву, но это не изменит результат (как вы сказали, это больше похоже на continue)

По аналогичному ответу.

Отключить array_walk от анонимной функции

Лучший и худший способ завершить это - Исключение. Не рекомендую этот способ, но это решение вашего вопроса:

      try {
    $result = array_reduce( $input, function ( $carrier, $item ) {
        // do the $carrier stuff
        $condition = true;
        if ( $condition ) {
            throw new Exception;
        }

        return $carrier;
    } );
} catch ( Exception $exception ) {
    echo 'Break';
}

Как бы я решил проблему

Я бы создал глобальную функцию или написал расширение PHP и добавил бы функцию

Есть хороший ответ о написании расширения PHP:Как сделать расширение PHP

      array_reduce_2();

Но есть проблема с нарушением реализации. Необходимо определить, какое состояние не работает. Ниже реализации проверяет, вернулся ли обратный вызов. Если так - выход из строя.

Этот способ позволяет проверить, прервалось ли выполнение, проверив тип возвращаемого значения.

реализация array_reduce_2

      function array_reduce_2( $array, $callback, $initial = null ) {
    $len   = count( $array );
    $index = 0;

    if ( $len == 0 && count( func_get_args() ) == 1 ) {
        return null;
    }

    $result = $initial ?? $array[ $index ++ ];
    for ( ; $index < $len; $index ++ ) {
        $result = $callback( $result, $array[ $index ] );

        if ( ! is_callable( $result ) ) {
            continue;
        }

        // break;
        return $result;
    }

    return $result;

Я использовал идею реализации JS и соответствующим образом переписал для PHP. https://gist.github.com/keeto/229931

Обнаружение, если произошел обрыв

      $input  = [ 'test', 'array', 'god was one of us' ];
$result = array_reduce_2( $input, function ( $carrier, $item ) {
    // do the $carrier stuff
    if ( $item === 'god was one of us' ) {
        return function () {
            return 'god was one of us';
        };
    }

    return $carrier;
} );

$is_break = is_callable( $result );
if ( $is_break ) {
    echo $result();
    exit;
}

Важно отметить!

Этот array_reduce_2 реализация работает правильно, только если вам не нужно возвращать нормальное значение в качестве обратного вызова.

Во-первых, позвольте мне сказать, что array_reduce, вероятно, мои любимые функции - я известен (ну, в очень маленьком кругу) тем, что взял 40 строк четко написанного кода и заменил их четырьмя более сложными 10-строчными вызовами array_reduce для выполнения то же самое!

К сожалению, функции массива PHP, похоже, обязательно захотят выполнить свою задачу. Это, в сочетании с невозможностью создать рекурсивную безымянную функцию, затрудняет решение этой общей ситуации. Не желая помещать в свой код много уродливых циклов for, я стараюсь похоронить их в другой функции (см. Сокращение ниже), как это было на предыдущем плакате.

Стоит отметить, что это никоим образом не так эффективно, как использование функций массива, и в большинстве случаев лучше просто позволить функции сокращения массива использовать флаг «готово» для быстрого просмотра ненужных значений. Во всяком случае, это что-то вроде array_reduce (функция оценки, использующая нулевой возврат, чтобы указать, что она закончена). Цель состоит в том, чтобы сложить числа в массиве, пока вы не дойдете до 4.

      <?php

$init = 0;
$arr = [1,2,3,4,5,6,7,8,9,0];

$func = function($c, $it) {
            if ($it == 4) return null;
            return $c + $it;
        };

function reduce($arr, $f, $init) {
    for ($c = $init; count($arr); ) {
        $newc = $f($c, array_shift($arr));
        if (!isset($newc)) break;
        $c = $newc;
    }
    return $c;
}

echo reduce($arr, $func, $init) . "\n";   // 6

Я предлагаю использовать foreachпетли вместо этого. Причины не использовать array_reduce находятся:

Звуковые причины:

  1. Тип не проверяется статически. Таким образом, проверки кода не показывают ошибок типа, если они есть во входных аргументах или аргументах обратного вызова.
  2. Он возвращается mixed, поэтому проверки не выявляют ошибок, если вы неправильно используете результат, или они могут показать ложное срабатывание, если вы используете его правильно.
  3. Вы не можете сломаться.

Мнения причин:

  1. Это тяжелее для глаз. Иметь $result и добавление к нему в цикле (или что бы вы ни делали) легче читать, чем понять, что что-то returnЭд, а затем прошел как $carry аккумулятор в следующем вызове.
  2. Мне лень правильно извлекать функции. Если я извлеку одну операцию для обратного вызова, я могу найти код достаточно коротким, чтобы не извлекать всю операцию с массивом в функцию, что действительно должно быть выполнено в первую очередь.
  3. Если вы используете условие для прерывания, есть большая вероятность, что однажды вам могут понадобиться другие аргументы для этой функции обратного вызова. При фиксированной сигнатуре обратного вызова вам придется передавать аргументы с помощью use ключевое слово, которое на самом деле намного сложнее читать, чем не обратное.

breakable_array_reduce()

      function breakable_array_reduce(array $array, callable $callback, $initial = null) {
    $result = $initial;
    foreach ($array as $value) {
        $ret = $callback($result, $value);
        if (false === $ret) {
            return $result;
        } else {
            $result = $ret;
        }
    }
    return $result;
}

использование

      // array of 10 values
$arr = [
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1
];

// callback function which stops once value >= 5
$sum_until_five = function($initial, $value) {
    if ($initial >= 5) {
        return false;
    } else {
        return $initial + $value;
    }
};

// calculate sum of $arr using $sum_until_five()
$sum = breakable_array_reduce($arr, $sum_until_five);

// output: 5
echo $sum;

Объяснение

breakable_array_reduce() будет работать так же, как array_reduce() если / до обратного вызова $callback возвращается bool(false)

Альтернативная реализация с использованием ключей массива:

breakable_array_reduce_keyed ()

      function breakable_array_reduce_keyed(array $array, callable $callback, $initial = null) {
    $result = $initial;
    foreach ($array as $key => $value) {
        $ret = $callback($result, $value, $key);
        if (false === $ret) {
            return $result;
        } else {
            $result = $ret;
        }
    }
    return $result;
}

использование

      // array of values
$arr = [
    'foo' => 1,
    'bar' => 1,
    'baz' => 1
];

// callback function which stops when $key === 'baz'
$sum_until_baz = function($initial, $value, $key) {
    if ('baz' === $key) {
        return false;
    } else {
        return $initial + $value;
    }
};

// calculate sum of $arr using $sum_until_baz()
$sum = breakable_array_reduce($arr, $sum_until_baz);

// output: 2
echo $sum;

PS Я только что написал и полностью протестировал это локально.

Другие вопросы по тегам