PHP:公开“获取”;并“设置”对于具有嵌套关联数组的对象

发布于 2024-10-30 06:13:58 字数 1988 浏览 7 评论 0原文

我有一个使用多级关联数组存储值的类:

我需要添加一种访问和修改嵌套值的方法。这是解决我的问题的可行解决方案,但是速度相当慢。有没有更好的方法来做到这一点

注意: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' 相同,替代标点符号是为了清楚起见。

I have a class which stores values with a multi-level associative array:

I need to add a way to access and modify nested values. Here is a working solution for my problem, but it is rather slow. Is there a better way of doing this?

Note: The use of get / set functions is not mandatory, but there needs to be an efficient way to define a default value.

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

}

Which would be used as follows:

$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

Note: 'general.0.something' is the same as 'general#0.something', the alternative punctuation is for the purpose of clarity.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

向地狱狂奔 2024-11-06 06:13:58

嗯,这个问题很有趣,我忍不住再修改一下。 :-)

所以,这是我的结论。 您的实现可能是最直接和清晰的。而且它正在工作,所以我不会真正费心去寻找另一个解决方案。事实上,你最终会接到多少电话?性能上的差异是否值得这么麻烦(我的意思是“超级快”和“几乎一半快”)?

不过,如果性能确实是一个问题(收到数千个调用),那么如果您重复查找数组,就有一种方法可以减少执行时间。

在您的版本中,最大的负担落在 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 尚未实现,您必须修改它以使用替代语法(# 分隔符),但我认为它传达了这个想法。

至少在我的测试平台上,与原始实现相比,执行此操作需要一半的时间。仍然原始数组访问速度更快,但在我的情况下差异约为 30-40%。目前这是我能达到的最好成绩。我希望你的实际情况还不够大,以至于我在途中遇到了一些内存限制。 :-)

Well, the question was interesting enough that I couldn't resist tinkering a bit more. :-)

So, here are my conclusions. Your implementation is probably the most straightforward and clear. And it's working, so I wouldn't really bother about searching for another solution. In fact, how much calls are you gonna get in the end? Is the difference in performance worth the trouble (I mean between "super ultra blazingly fast" and "almost half as fast")?

Put aside though, if performance is really an issue (getting thousands of calls), then there's a way to reduce the execution time if you repetitively lookup the array.

In your version the greatest burden falls on string operations in your get function. Everything that touches string manipulation is doomed to fail in this context. And that was indeed the case with all my initial attempts at solving this problem.

It's hard not to touch strings if we want such a syntax, but we can at least limit how much string operations we do.

If you create a hash map (hash table) so that you can flatten your multidimensional array to a one level deep structure, then most of the computations done are a one time expense. It pays off, because this way you can almost directly lookup your values by the string provided in your get call.

I've come up with something roughly like this:

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

?>

The setter is not yet implemented, and you would have to modify it for alternative syntax (# separator), but I think it conveys the idea.

At least on my testbed it takes half the time to execute this compared to the original implementation. Still raw array access is faster, but the difference in my case is around 30-40%. At the moment that was the best I could achieve. I hope that your actual case is not big enough that I've hit some memory constraints on the way. :-)

乖乖哒 2024-11-06 06:13:58

好吧,我的第一次尝试没有达到我的目标。这是使用本机 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);
    }
}

初始化

$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'

Ok, my first approached missed the goal I was aiming for. Here is the solution to using native PHP array syntax (at least for access) and still being able to set a default value.

Update: Added missing functionality for get/set and on the fly converting.

By the way, this is not an approach to take if you are optimizing for performance. This is perhaps 20 times slower than regular array access.

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

Result

$something = $test['general'][0]['something']; // 'Hello World!'
$notfound = $test['general'][0]['notfound']; // 'Default Value'
太阳公公是暖光 2024-11-06 06:13:58

您正在寻找类似的东西吗?本质上,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);

You're looking for something like that? Essentially the get() method uses references to descend into the $values array and breaks out of the method if a requirement could not be met.

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);
原野 2024-11-06 06:13:58

这就是 PHP 中多维数组的一般工作方式:

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

接收 Hello World

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

This is how multidimensional array work in general in PHP:

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

To receive Hello World:

echo $data['general'][0]['something'];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文