Как отфильтровать массив для удаления родителей с дочерними элементами, равными нулю? - PullRequest
2 голосов
/ 03 февраля 2010

У меня есть массив, структурированный как:

$something = array(
    0 => array(
        'label' => 'Foo',
        'items' => array(
            '123' => 4,
            '124' => 0,
        )
    ),
    1 => array(
        'label' => 'Bar',
        'items' => array(
            '125' => 5,
            '126' => 1,
        )
    ),
    2 => array(
        'label' => 'Baz',
        'items' => array(
            '127' => 0,
            '128' => 0,
        )
    )
);

И мне нужно удалить все ключи 'items' со значением ноль, а если у элементов нет дочерних элементов, удалить весь блок.

Итак, после фильтрации этого массива у меня должно быть:

array(2){
    [0]=>
    array(2) {
        ["label"]=> "Foo"
        ["items"]=>
            array(1) {
                [123]=> 4
            }
    }
    [1]=>
    array(2) {
    ["label"]=> "Bar"
    ["items"]=>
        array(2) {
            [125]=> 5
            [126]=> 1
        }
    }
}

Я пытался использовать array_filter, array_walk и array_walk_recursive (это работает хорошо - но - не позволяет мне удалить ключи вфункция обратного вызова ..) безуспешно ..

Нужно ли мне деконструировать и перестраивать новый массив, или мне не хватает правильного использования функций array_ *?

Ответы [ 3 ]

4 голосов
/ 03 февраля 2010
$something = array( .. ); // as defined above

for ( $i = 0, $iMax = count( $something ); $i < $iMax; $i++ )
{
    foreach ( $something[$i]['items'] as $key => $value )
    {
        if ( !$value )
            unset( $something[$i]['items'][$key] );
    }

    if ( count( $something[$i]['items'] ) == 0 )
        unset( $something[$i] );
}
$something = array_values( $something ); // reset indices
2 голосов
/ 03 февраля 2010

Хорошо, теперь это специально для вашего массива. Не ожидайте, что он будет работать с произвольными структурами массивов:

class ItemFilterIterator extends RecursiveFilterIterator
{
    public function accept()
    {
        if(is_numeric($this->key()) && is_array($this->current())) {
            if(array_key_exists('items', $this->current())) {
                $items = $this->current();
                return array_sum($items['items']) > 0;
            }
        } elseif(is_numeric($this->key()) && $this->current() === 0 ) {
            return false;
        }
        return true;
    }
}

При итерации по массиву все элементы передаются методу accept() из ItemFilterIterator, который проверяет, является ли ключ элемента current числовым. Это относится только к элементам верхнего уровня и элементам внутри items . Если текущий элемент является массивом, проверьте, существует ли элемент с ключом items и если сумма значений детей больше нуля. Если нет, пропустите элемент в итерации. Если это не массив, а числовое значение, а значение равно нулю, предположим, что мы находимся в пределах items и пропустите эти элементы.

Вы используете это так:

$iterator = new RecursiveIteratorIterator(
                new ItemFilterIterator(
                    new RecursiveArrayIterator($something)));

foreach($iterator as $key => $value) {
    echo $key, '--', $value, PHP_EOL; // or whatever else you want to do here
}

Это было забавное упражнение:)

Подробнее о SplIterators:

1 голос
/ 03 февраля 2010

Я не могу найти способ сделать это с помощью array_walk_recursive, поэтому я просто выбрал бы что-то вроде этого:

/**
 * Removes values from an array if the callback function is true.
 * Removes empty child arrays
 */
function array_remove_recursive(array $haystack, $f){
    if ( empty($haystack) ){
        return $haystack;
    }
    foreach ( $haystack as $key => $val ){
        if ( is_array($val){
            $haystack[$key] = array_remove_recursive($val);
            if ( empty($haystack[$key]){
                unset($haystack[$key]);
            }
        }elseif ( $f($val) === true ){
            unset($haystack[$key]);
        }
    }
    return $haystack;
}

Основываясь на принципе «каждая функция выполняет одно и только одно», вероятно, предпочтительнее разделить ее на две функции: одну для удаления элемента, если функция возвращает true, а другую для удаления пустых дочерних элементов. У этого есть обратная сторона необходимости проходить через массив дважды.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...