如何通过键名/路径访问和操作多维数组?

Posted

技术标签:

【中文标题】如何通过键名/路径访问和操作多维数组?【英文标题】:How to access and manipulate multi-dimensional array by key names / path? 【发布时间】:2015-03-11 20:46:10 【问题描述】:

我必须在 php 中实现一个 setter,它允许我指定数组(目标)的键或子键,将名称作为点分隔键传递价值。

给定以下代码:

$arr = array('a' => 1,
             'b' => array(
                 'y' => 2,
                 'x' => array('z' => 5, 'w' => 'abc')
             ),
             'c' => null);

$key = 'b.x.z';
$path = explode('.', $key);

$key的值我想达到$arr['b']['x']['z']的值5

现在,给定一个变量值$key 和一个不同的$arr 值(具有不同的深度)。

如何设置$key引用的元素的值?

对于getterget()我写了这段代码:

public static function get($name, $default = null)

    $setting_path = explode('.', $name);
    $val = $this->settings;

    foreach ($setting_path as $key) 
        if(array_key_exists($key, $val)) 
            $val = $val[$key];
         else 
            $val = $default;
            break;
        
    
    return $val;

写一个 setter 比较困难,因为我成功地到达了正确的元素(来自$key),但我无法在原始数组中设置值,我没有'不知道如何一次指定所有键。

我应该使用某种回溯吗?或者我可以避免吗?

【问题讨论】:

这能回答你的问题吗? use strings to access (potentially large) multidimensional arrays 【参考方案1】:

假设$path 已经是一个通过explode 的数组(或添加到函数中),那么您可以使用引用。如果$path 等无效,您需要添加一些错误检查(想想isset):

$key = 'b.x.z';
$path = explode('.', $key);

吸气剂

function get($path, $array) 
    //$path = explode('.', $path); //if needed
    $temp =& $array;

    foreach($path as $key) 
        $temp =& $temp[$key];
    
    return $temp;


$value = get($path, $arr); //returns NULL if the path doesn't exist

设置者/创建者

此组合将在现有数组中设置一个值,或者如果您传递一个尚未定义的数组,则创建该数组。确保将$array 定义为通过引用&$array 传递:

function set($path, &$array=array(), $value=null) 
    //$path = explode('.', $path); //if needed
    $temp =& $array;

    foreach($path as $key) 
        $temp =& $temp[$key];
    
    $temp = $value;


set($path, $arr);
//or
set($path, $arr, 'some value');

取消设置

这将unset路径中的最后一个键:

function unsetter($path, &$array) 
    //$path = explode('.', $path); //if needed
    $temp =& $array;

    foreach($path as $key) 
        if(!is_array($temp[$key])) 
            unset($temp[$key]);
         else 
            $temp =& $temp[$key];
        
    

unsetter($path, $arr);

*最初的答案有一些有限的功能,如果它们对某人有用,我会留下它们:

二传手

确保将$array 定义为通过引用&$array 传递:

function set(&$array, $path, $value) 
    //$path = explode('.', $path); //if needed
    $temp =& $array;

    foreach($path as $key) 
        $temp =& $temp[$key];
    
    $temp = $value;


set($arr, $path, 'some value');

或者如果你想返回更新后的数组(因为我很无聊):

function set($array, $path, $value) 
    //$path = explode('.', $path); //if needed
    $temp =& $array;

    foreach($path as $key) 
        $temp =& $temp[$key];
    
    $temp = $value;

    return $array;


$arr = set($arr, $path, 'some value');

创作者

如果您不想创建数组并选择设置值:

function create($path, $value=null) 
    //$path = explode('.', $path); //if needed
    foreach(array_reverse($path) as $key) 
        $value = array($key => $value);
    
    return $value;
    

$arr = create($path);    
//or
$arr = create($path, 'some value');

为了好玩

给定字符串b.x.z,构造和评估类似$array['b']['x']['z']的东西:

function get($array, $path) 
    //$path = explode('.', $path); //if needed
    $path = "['" . implode("']['", $path) . "']";
    eval("\$result = \$array$path;");

    return $result;

设置类似$array['b']['x']['z'] = 'some value';:

function set(&$array, $path, $value) 
    //$path = explode('.', $path); //if needed
    $path = "['" . implode("']['", $path) . "']";
    eval("\$array$path = $value;");

取消设置类似$array['b']['x']['z']:

function unsetter(&$array, $path) 
    //$path = explode('.', $path); //if needed
    $path = "['" . implode("']['", $path) . "']";
    eval("unset(\$array$path);");

【讨论】:

你知道吗?我自己编写了相同的代码,但是我什至没有对其进行测试就将其丢弃,因为我认为每次将值重新分配给同一个变量时也会改变中间的数组值(在达到所需的值之前),从而引发不可预测的一面效果!相反,只要使用=&,它就是安全的。是我对参考作业的误解的原因!最后比我想象的要容易,我自己也能做到^_^谢谢 做其他测试,我注意到这里我们需要通过引用分配两次,要么是在函数参数声明中 AND 在函数分配给@987654347 @。我想知道是否有人认为这很丑陋。 --- 我希望,通过引用传递参数,$array 变量应该通过引用分配,如果我会这样做 $temp = $array (因为源参数也是通过引用)。那是因为函数签名中的 pass-by-ref 只是为了防止复制$array?还是有其他原因? (很抱歉仍然为菜鸟问题而烦恼)。 @AbraCadaver,您关闭为重复的另一个问题是该问题的确切补充。这里给出了嵌套数组,他问如何从不同的输入格式创建它:***.com/questions/34886008/… 优秀!解决了三天的问题。谢谢。 是否也可以unset()引用数组?我的意思是,我可以写$temp = NULL;,但我更愿意写unset()it。 unset(&$temp); 不起作用。【参考方案2】:

我不是在纯 PHP 中为您提供解决方案,而是使用 ouzo goodies 具体为 Arrays::getNestedValue 方法:

$arr = array('a' => 1,
    'b' => array(
        'y' => 2,
        'x' => array('z' => 5, 'w' => 'abc')
    ),
    'c' => null);

$key = 'b.x.z';
$path = explode('.', $key);

print_r(Arrays::getNestedValue($arr, $path));

同样如果你需要设置嵌套值,你可以使用Arrays::setNestedValue方法。

$arr = array('a' => 1,
    'b' => array(
        'y' => 2,
        'x' => array('z' => 5, 'w' => 'abc')
    ),
    'c' => null);

Arrays::setNestedValue($arr, array('d', 'e', 'f'), 'value');
print_r($arr);

【讨论】:

【参考方案3】:

我有一个我经常使用的实用程序,我会分享。不同之处在于它使用数组访问表示法(例如b[x][z])而不是点表示法(例如b.x.z)。借助文档和代码,它是不言自明的。

<?php
class Utils 
    /**
     * Gets the value from input based on path.
     * Handles objects, arrays and scalars. Nesting can be mixed.
     * E.g.: $input->a->b->c = 'val' or $input['a']['b']['c'] = 'val' will
     * return "val" with path "a[b][c]".
     * @see Utils::arrayParsePath
     * @param mixed $input
     * @param string $path
     * @param mixed $default Optional default value to return on failure (null)
     * @return NULL|mixed NULL on failure, or the value on success (which may also be NULL)
     */
    public static function getValueByPath($input,$path,$default=null) 
        if ( !(isset($input) && (static::isIterable($input) || is_scalar($input))) ) 
            return $default; // null already or we can't deal with this, return early
        
        $pathArray = static::arrayParsePath($path);
        $last = &$input;
        foreach ( $pathArray as $key ) 
            if ( is_object($last) && property_exists($last,$key) ) 
                $last = &$last->$key;
             else if ( (is_scalar($last) || is_array($last)) && isset($last[$key]) ) 
                $last = &$last[$key];
             else 
                return $default;
            
        
        return $last;
    

    /**
     * Parses an array path like a[b][c] into a lookup array like array('a','b','c')
     * @param string $path
     * @return array
     */
    public static function arrayParsePath($path) 
        preg_match_all('/\\[([^[]*)]/',$path,$matches);
        if ( isset($matches[1]) ) 
            $matches = $matches[1];
         else 
            $matches = array();
        
        preg_match('/^([^[]+)/',$path,$name);
        if ( isset($name[1]) ) 
            array_unshift($matches,$name[1]);
         else 
            $matches = array();
        
        return $matches;
    

    /**
     * Check if a value/object/something is iterable/traversable, 
     * e.g. can it be run through a foreach? 
     * Tests for a scalar array (is_array), an instance of Traversable, and 
     * and instance of stdClass
     * @param mixed $value
     * @return boolean
     */
    public static function isIterable($value) 
        return is_array($value) || $value instanceof Traversable || $value instanceof stdClass;
    


$arr = array('a' => 1,
             'b' => array(
                 'y' => 2,
                 'x' => array('z' => 5, 'w' => 'abc')
             ),
             'c' => null);

$key = 'b[x][z]';

var_dump(Utils::getValueByPath($arr,$key)); // int 5

?>

【讨论】:

真的很有用!我已经遇到过类似的事情,遵循相同的符号。我比较一下代码,谢谢!当然,这种表示法将提供更大的灵活性。我随机决定点,因为我的用例不需要很复杂。 @Kamafeather,您仍然可以使用点表示法,只需将 arrayParsePath 方法更改为 return explode('.',$path); -- 或者添加一个测试以查看您应该使用哪个路径解析器并实现两者! 是的,应该不难。我想直接在正则表达式上工作(并可选择将其作为参数传递给getValueByPath())会是更好的方法!无论如何,很好的建议,谢谢! 在 PHP else if 中作为两个词违反了 PSR-12 标准;应该是一个字elseif @mickmackusa PSR-12 是在此回答后 7 个月(2015 年 8 月)提出,并在 4 年零 7 个月后(2019 年 8 月)获得批准; lmgtfy php-fig.org/psr/psr-12/meta。作为拥有 30k 声望的会员,欢迎您提出符合 PSR-12 的编辑建议。【参考方案4】:

作为“吸气剂”,我过去使用过这个:

$array = array('data' => array('one' => 'first', 'two' => 'second'));

$key = 'data.one';

function find($key, $array) 
    $parts = explode('.', $key);
    foreach ($parts as $part) 
        $array = $array[$part];
    
    return $array;


$result = find($key, $array);
var_dump($result);

【讨论】:

【参考方案5】:

如果数组的键是唯一的,可以使用array_walk_recursive几行代码解决问题:

    $arr = array('a' => 1,
        'b' => array(
            'y' => 2,
            'x' => array('z' => 5, 'w' => 'abc')
        ),
        'c' => null);

    function changeVal(&$v, $key, $mydata) 
        if($key == $mydata[0]) 
            $v = $mydata[1];
        
    

    $key = 'z';
    $value = '56';
    array_walk_recursive($arr, 'changeVal', array($key, $value));

    print_r($arr);

【讨论】:

不幸的是,我的情况不能保证密钥是唯一的。但我会继续关注这一点。谢谢【参考方案6】:

这是一种使用静态类的方法。这种风格的好处是您的配置将在您的应用程序中全局访问。

它的工作原理是获取一个关键路径,例如“database.mysql.username”,并将字符串拆分为每个关键部分,然后移动一个指针来创建一个嵌套数组。

这种方法的好处是您可以提供部分键并获取配置值数组,而不仅限于最终值。它还使“默认值”的实现变得微不足道。

如果您想拥有多个配置存储,只需删除静态关键字并将其用作对象即可。

Live Example

class Config

    private static $configStore = [];
    // This determines what separates the path
    // Examples: "." = 'example.path.value' or "/" = 'example/path/value'
    private static $separator = '.';

    public static function set($key, $value)
    
        $keys = explode(self::$separator, $key);

        // Start at the root of the configuration array
        $pointer = &self::$configStore;

        foreach ($keys as $keySet) 
            // Check to see if a key exists, if it doesn't, set that key as an empty array
            if (!isset($pointer[$keySet])) 
                $pointer[$keySet] = [];
            

            // Set the pointer to the current key
            $pointer = &$pointer[$keySet];
        

        // Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
        $pointer = $value;
    

    public static function get($key, $defaultValue = null)
    
        $keys = explode(self::$separator, $key);

        // Start at the root of the configuration array
        $pointer = &self::$configStore;

        foreach ($keys as $keySet) 
            // If we don't have a key as a part of the path, we should return the default value (null)
            if (!isset($pointer[$keySet])) 
                return $defaultValue;
            
            $pointer = &$pointer[$keySet];
        

        // Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
        return $pointer;
    


// Examples of how to use
Config::set('database.mysql.username', 'exampleUsername');
Config::set('database.mysql.password', 'examplePassword');
Config::set('database.mysql.database', 'exampleDatabase');
Config::set('database.mysql.host', 'exampleHost');

// Get back all the database configuration keys
var_dump(Config::get('database.mysql'));

// Get back a particular key from the database configuration
var_dump(Config::get('database.mysql.host'));

// Get back a particular key from the database configuration with a default if it doesn't exist
var_dump(Config::get('database.mysql.port', 3306));

【讨论】:

【参考方案7】:

此函数与接受的答案相同,此外还通过引用添加第三个参数,如果密钥存在则设置为 true/false

function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) 
  $ref = &$array;
  foreach ($parents as $parent) 
    if (is_array($ref) && array_key_exists($parent, $ref)) 
      $ref = &$ref[$parent];
    
    else 
      $key_exists = FALSE;
      $null = NULL;
      return $null;
    
  
  $key_exists = TRUE;
  return $ref;

【讨论】:

【参考方案8】:

我有一个非常简单和肮脏的解决方案(非常肮脏!如果密钥的值不受信任,请勿使用!)。它可能比遍历数组更有效。

function array_get($key, $array) 
    return eval('return $array["' . str_replace('.', '"]["', $key) . '"];');


function array_set($key, &$array, $value=null) 
    eval('$array["' . str_replace('.', '"]["', $key) . '"] = $value;');

这两个函数都在代码的 sn-p 上执行eval,其中键被转换为作为 PHP 代码的数组元素。并返回或设置对应键的数组值。

【讨论】:

【参考方案9】:

getter 的另一种解决方案,使用普通的array_reduce 方法

@AbraCadaver 的解决方案不错,但不完整:

缺少可选的分隔符参数和拆分(如果需要) 如果尝试从像'one.two' 这样的标量值从['one' =&gt; 2] 获取键,则会引发错误

我的解决办法是:

function get ($array, $path, $separator = '.') 
    if (is_string($path)) 
        $path = explode($separator, $path);
    

    return array_reduce(
        $path,
        function ($carry, $item) 
            return $carry[$item] ?? null;
        ,
        $array
    );

由于?? 运算符,它需要 PHP 7,但是对于旧版本可以很容易地更改它...

【讨论】:

【参考方案10】:

这里是访问和操作 MD 数组的简单代码。但是没有证券。

二传手:

eval('$vars = &$array["' . implode('"]["', explode('.', strtolower($dot_seperator_path))) . '"];');
$vars = $new_value;

吸气剂:

eval('$vars = $array["' . implode('"]["', explode('.', strtolower($dot_seperator_path))) . '"];');
return $vars;

【讨论】:

以上是关于如何通过键名/路径访问和操作多维数组?的主要内容,如果未能解决你的问题,请参考以下文章

使用Jquery在多维数组中增加键名

如何循环访问和访问多维和关联数组中的各种元素? PHP,JSON 或 XML

php 多个多维数组求交集

thinkphp 多维数组,如何转成以为一维数组,然后写入数据库

PHP array_multisort—对多个数组或多维数组进行排序

Perl Foreach 通过多维数组?