【问题标题】:How to create data structure with key duplicates in php instead of default hash?如何在 php 中创建具有重复键而不是默认哈希的数据结构?
【发布时间】:2026-01-17 10:25:01
【问题描述】:

我想创建包装类,这将启用密钥重复,而默认哈希不允许它。类应该使用php5中引入的成员重载机制,所以它会模仿标准hash的所有行为。例如,我想拥有 smth like

$var => obj( :values_arr -> array(
      obj(:key -> 'mykey', :value -> 'val1'), 
      obj(:key -> 'mykey', :value -> 'val2')
    )
)

如果我想得到 $var['mykey'],它应该返回 array('val1', 'val2'),但是如果我想用新的 'mykey' => 'value' 对扩展 obj,我会打电话

$val['mykey'][] = 'value'

主要思想是保留哈希的行为,并且在尝试使用现有键分配值后,它不会被覆盖,而是附加到列表中。

你将如何模仿php5(5.3之前)中的其他数据结构?您有什么已知的解决方案或示例要分享吗?

【问题讨论】:

    标签: php spl


    【解决方案1】:

    喜欢这个

    class MultiMap
    {
        protected $map = array();
    
        function __set($key, $val) {
            if(!isset($this->map[$key]))  
               return $this->map[$key] = $val;
            if(!is_array($this->map[$key]))
               $this->map[$key] = array($this->map[$key]);
            $this->map[$key][] = $val;
        }
        function __get($key) {
           return $this->map[$key];
        }
    }
    
    $m = new MultiMap;
    $m->foo = 1;
    $m->foo = 2;
    $m->bar = 'zzz';
    print_r($m->foo);
    print_r($m->bar);
    

    但整个想法对我来说有点奇怪。你能解释一下为什么需要这个吗?

    我不清楚为什么需要运算符作为 AST 中的键 也许这样的结构会更方便

       ('op' => 'AND', 'args' => [
            (op => AND, args => [
                (op  => atom, value => word1),
                (op  => atom, value => word2),
            ]),
            (op => AND, args => [
                (op  => atom, value => word3),
                (op  => atom, value => word4),
            ])
        ])
    

    【讨论】:

    • 我需要这个用于抽象语法树构建器。例如,我可能有表达式 ((word1 AND word2) AND (word3 AND word4))。我会转换成数组array('AND' => array('AND' => array('word1', 'word2'), 'AND' => array('word3', 'word4'));
    【解决方案2】:

    可以实现数组语法

    $val['mykey'] = 'value';
    

    使用ArrayAccess 接口

    class MultiHash implements ArrayAccess, IteratorAggregate
    {
        protected $data;
    
        public function offsetGet($offset)
        {
            return $this->data[$offset];
        }
        public function offsetSet($offset, $value)
        {
            if ($offset === null) { // $a[] = ...
                $this->data[] = array($value);
            } else {
                $this->data[$offset][] = $value;
            }
        }
        public function offsetExists($offset)
        {
            return isset($this->data[$offset]);
        }
        public function offsetUnset($offset)
        {
            unset($this->data[$offset]);
        }
    
        public function getIterator()
        {
            $it = new AppendIterator();
            foreach ($this->data as $key => $values) {
                $it->append(new ConstantKeyArrayIterator($values, 0, $key));
            }
            return $it;
        }
    }
    
    class ConstantKeyArrayIterator extends ArrayIterator
    {
        protected $key;
    
        public function __construct($array = array(), $flags = 0, $key = 0)
        {
            parent::__construct($array,$flags);
            $this->key = $key;
        }
        public function key()
        {
            return parent::key() === null ? null : $this->key;
        }
    }
    

    我还实现了IteratorAggregate 以允许对所有单个元素进行迭代。

    测试代码

    $test = new MultiHash();
    $test[] = 'foo';
    $test[] = 'bar';
    $test['mykey'] = 'val1';
    $test['mykey'] = 'val2';
    $test['mykey2'] = 'val3';
    
    echo "mykey: ";
    var_dump($test['mykey']);
    
    echo "mykey2: ";
    var_dump($test['mykey2']);
    
    echo "iterate:\n";
    foreach ($test as $key => $value) {
        echo "$key : $value \n";
    }
    

    测试输出

    mykey: array(2) {
      [0]=>
      string(4) "val1"
      [1]=>
      string(4) "val2"
    }
    mykey2: array(1) {
      [0]=>
      string(4) "val3"
    }
    iterate:
    0 : foo 
    1 : bar 
    mykey : val1 
    mykey : val2 
    mykey2 : val3 
    

    【讨论】: