我有一个用多级关联数组存储值的类:
我需要添加一种访问和修改嵌套值的方法。这里是我的问题的一个工作解决方案,但是它是相当慢。是否有更好的方法来做这个?
注意:使用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"answers"general#0"是一样的。,替代标点符号是为了清晰。
这个问题很有趣,我忍不住又做了一些修改。: -)
所以,这是我的结论。你的实现可能是最直接和清晰的。它是有效的,所以我就不用再费力去寻找另一个解了。事实上,你最后会接到多少电话?性能上的差异值得这么麻烦吗(我的意思是"超级超级快的"answers"几乎只有一半快的")?
先把放在一边,如果性能确实是个问题(获得数千个调用),那么如果您重复查找数组,则有一种方法可以减少执行时间。
在您的版本中,最大的负担落在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;
?>
setter尚未实现,您将不得不修改它以替代语法(#
separator),但我认为它传达了思想。至少在我的测试平台上,与原始实现相比,执行所需的时间是的一半。仍然原始数组访问速度更快,但在我的情况下差异约为30-40%。目前这是我能做到的最好成绩。我希望您的实际情况不是太大,我在此过程中遇到了一些内存限制。: -) 好吧,我的第一次尝试没有达到我的目标。下面是使用本地PHP数组语法的解决方案(至少对于访问),并且仍然能够设置默认值。
更新:添加了get/set和动态转换的缺失功能。
顺便说一下,如果您正在优化性能,这是而不是方法。这可能比常规的数组访问慢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'
你在找这样的东西吗?本质上,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);
这是PHP中多维数组的一般工作方式:
$data = array(
'general' => array(
0 => array(
'something' => 'Hello World!'
)
)
);
接收Hello World
:
echo $data['general'][0]['something'];