PHP: Предоставьте get и set для объекта с вложенными ассоциативными массивами - PullRequest
5 голосов
/ 05 апреля 2011

У меня есть класс, в котором хранятся значения с многоуровневым ассоциативным массивом:

Мне нужно добавить способ доступа и изменения вложенных значений. Вот рабочее решение моей проблемы, но это довольно медленно . Есть ли лучший способ сделать это ?

Примечание: использование функций get / set не является обязательным, но должен быть эффективный способ определения значения по умолчанию.

class Demo {
    protected $_values = array();

    function __construct(array $values) {
        $this->_values = $values;
    }

    public function get($name, $default = null) {
        $token = strtok($name, '.#');
        $node = $this->_values;
        while ($token !== false) {
            if (!isset($node[$token]))
                return $default;
            $node = $node[$token];
            $token = strtok('.#');
        }
        return $node;
    }

    public function set($name, $value) {
        $next_token = strtok($name, '.#');
        $node = &$this->_values;

        while ($next_token !== false) {
            $token = $next_token;
            $next_token = strtok('.#');

            if ($next_token === false) {
                $node[ $token ] = $value;
                break;
            }
            else if (!isset($node[ $token ]))
                $node[ $token ] = array();

            $node = &$node[ $token ];
        }

        unset($node);
    }

}

Что будет использоваться следующим образом:

$test = new Demo(array(
    'simple'  => 27,
    'general' => array(
        0 => array(
            'something'    => 'Hello World!',
            'message'      => 'Another message',
            'special'      => array(
                'number'       => 27
            )
        ),
        1 => array(
            'something'    => 'Hello World! #2',
            'message'      => 'Another message #2'
        ),
    )
));

$simple = $test->get('simple'); // === 27

$general_0_something = $test->get('general#0.something'); // === 'Hello World!'

$general_0_special_number = $test->get('general#0.special.number'); === 27

Примечание: 'general.0.something' - это то же самое, что 'general # 0.something', альтернативная пунктуация предназначена для ясности.

Ответы [ 4 ]

3 голосов
/ 06 апреля 2011

Ну, вопрос был достаточно интересным, так что я не мог удержаться, чтобы немного повозиться. : -)

Итак, вот мои выводы. Ваша реализация , вероятно, самая простая и понятная . И это работает, так что я бы не стал беспокоиться о поиске другого решения. На самом деле, сколько звонков ты получишь в конце? Стоит ли разница в производительности (я имею в виду " super ultra невероятно быстро " и " почти вдвое быстрее ")?

Если оставить в стороне, , если производительность действительно является проблемой (получение тысяч вызовов), то есть способ сократить время выполнения, если вы повторно просматриваете массив.

В вашей версии наибольшее бремя приходится на строковые операции в вашей функции get. Все, что касается манипуляции со строками , обречено на провал в этом контексте. И это действительно было так со всеми моими первыми попытками решить эту проблему.

Трудно не трогать строки , если нам нужен такой синтаксис, но мы можем по крайней мере ограничить количество операций со строками, которые мы делаем .

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

Я придумал что-то вроде этого:

<?php

class Demo {
    protected $_values = array();
    protected $_valuesByHash = array();

    function createHashMap(&$array, $path = null) {
        foreach ($array as $key => &$value) {
            if (is_array($value)) {
                $this->createHashMap($value, $path.$key.'.');
            } else {
                $this->_valuesByHash[$path.$key] =& $value;
            }
        }
    }

    function __construct(array $values) {
        $this->_values = $values;
        $this->createHashMap($this->_values);

        // Check that references indeed work
        // $this->_values['general'][0]['special']['number'] = 28;
        // print_r($this->_values);
        // print_r($this->_valuesByHash);
        // $this->_valuesByHash['general.0.special.number'] = 29;
        // print_r($this->_values);
        // print_r($this->_valuesByHash);
    }

    public function get($hash, $default = null) {
        return isset($this->_valuesByHash[$hash]) ? $this->_valuesByHash[$hash] : $default;
    }
}


$test = new Demo(array(
    'simple'  => 27,
    'general' => array(
        '0' => array(
            'something'    => 'Hello World!',
            'message'      => 'Another message',
            'special'      => array(
                'number'       => 27
            )
        ),
        '1' => array(
            'something'    => 'Hello World! #2',
            'message'      => 'Another message #2'
        ),
    )
));

$start = microtime(true);

for ($i = 0; $i < 10000; ++$i) {
    $simple = $test->get('simple', 'default');
    $general_0_something = $test->get('general.0.something', 'default');
    $general_0_special_number = $test->get('general.0.special.number', 'default');
}

$stop = microtime(true);

echo $stop-$start;

?>

Сеттер еще не реализован, и вам придется изменить его для альтернативного синтаксиса (разделитель #), но я думаю, что он передает идею.

По крайней мере, на моем тестовом стенде для выполнения этого требуется половину времени по сравнению с исходной реализацией. Тем не менее, доступ к необработанным массивам быстрее , но разница в моем случае составляет около 30-40%. На данный момент это было лучшее, чего я мог достичь. Я надеюсь, что ваше реальное дело недостаточно велико, чтобы я столкнулся с некоторыми ограничениями памяти на этом пути. : -)

3 голосов
/ 05 апреля 2011

Хорошо, мой первый приближенный пропустил цель, к которой я стремился. Вот решение с использованием собственного синтаксиса массива PHP (по крайней мере для доступа) и возможность установки значения по умолчанию.

Обновление: добавлен отсутствующий функционал для получения / установки и конвертации на лету.

Кстати, это , а не подход, который нужно использовать, если вы оптимизируете производительность. Это, возможно, в 20 раз медленнее, чем обычный доступ к массиву.

class Demo extends ArrayObject {
    protected $_default;
    public function __construct($array,$default = null) {
        parent::__construct($array);
        $this->_default = $default;
    }
    public function  offsetGet($index) {
        if (!parent::offsetExists($index)) return $this->_default;
        $ret = parent::offsetGet($index);
        if ($ret && is_array($ret)) {
            parent::offsetSet($index, $this->newObject($ret));
            return parent::offsetGet($index);
        }
        return $ret;
    }
    protected function newObject(array $array=null) {
        return new self($array,$this->_default);
    }
}

Init

$test = new Demo(array(
    'general' => array(
        0 => array(
            'something'    => 'Hello World!'
        )
    )
),'Default Value');

Результат

$something = $test['general'][0]['something']; // 'Hello World!'
$notfound = $test['general'][0]['notfound']; // 'Default Value'
1 голос
/ 05 апреля 2011

Вы ищете что-то подобное?По сути, метод get() использует ссылки для спуска в массив $values и выходит из метода, если требование не может быть выполнено.

class Demo {
    protected $_values = array();

    public function __construct(array $values) {
        $this->_values = $values;
    }

    public function get($name, $default = null) {
        $parts  = preg_split('/[#.]/', $name);
        if (!is_array($parts) || empty($parts)) {
            return null;
        }

        $value  = &$this->_values;
        foreach ($parts as $p) {
            if (array_key_exists($p, $value)) {
                $value  = &$value[$p];
            } else {
                return null;
            }
        }

        return $value;
    }

    /**
     * setter missing
     */
}

$test = new Demo(array(
    'simple'  => 2,
    'general' => array(
        0 => array(
                'something'    => 'Hello World!',
                'message'      => 'Another message',
                'special'      => array(
                    'number'       => 4
                )
            ),
        1 => array(
                'something'    => 'Hello World! #2',
                'message'      => 'Another message #2'
            )
    )
));

$v = $test->get('simple'); 
var_dump($v);

$v = $test->get('general'); 
var_dump($v);

$v = $test->get('general.0'); 
var_dump($v);

$v = $test->get('general#0'); 
var_dump($v);

$v = $test->get('general.0.something'); 
var_dump($v);

$v = $test->get('general#0.something'); 
var_dump($v);

$v = $test->get('general.0.message'); 
var_dump($v);

$v = $test->get('general#0.message'); 
var_dump($v);

$v = $test->get('general.0.special'); 
var_dump($v);

$v = $test->get('general#0.special'); 
var_dump($v);

$v = $test->get('general.0.special.number'); 
var_dump($v);

$v = $test->get('general#0.special.number'); 
var_dump($v);

$v = $test->get('general.1'); 
var_dump($v);

$v = $test->get('general#1'); 
var_dump($v);

$v = $test->get('general.1.something'); 
var_dump($v);

$v = $test->get('general#1.something'); 
var_dump($v);

$v = $test->get('general.1.message'); 
var_dump($v);

$v = $test->get('general#1.message'); 
var_dump($v);
0 голосов
/ 05 апреля 2011

Вот как в PHP работает многомерный массив в целом:

$data = array(
    'general' => array(
         0 => array(
             'something'    => 'Hello World!'
         )
    )
);

Для получения Hello World:

echo $data['general'][0]['something'];
...