将数组/对象树的键转换为小写

3

我目前正在优化一个PHP应用程序,发现有一个函数被调用了大约10-20k次,所以我想从那里开始进行优化:

function keysToLower($obj)
{
        if(!is_object($obj) && !is_array($obj)) return $obj;
        foreach($obj as $key=>$element)
        {
                $element=keysToLower($element);
                if(is_object($obj))
                {
                        $obj->{strtolower($key)}=$element;
                        if(!ctype_lower($key)) unset($obj->{$key});
                }
                else if(is_array($obj) && ctype_upper($key))
                {
                        $obj[strtolower($key)]=$element;
                        unset($obj[$key]);
                }
        }
        return $obj;
}

大部分时间都花在递归调用上(在PHP中相当慢),但我没有看到将其转换为循环的方法。你会怎么做?
此版本不考虑关联数组,因为我的数据没有任何关联数组,但比原始版本快近10倍。大部分工作是由Gumbo完成的,主要加速来自使用引用和创建新对象而不是取消旧键。
function &keysToLower(&$obj)
{
    if(is_object($obj))
    {
        $newobj = (object) array();
        foreach ($obj as $key => &$val)
            $newobj->{strtolower($key)} = keysToLower($val);
        $obj=$newobj;
    }
    else if(is_array($obj))
        foreach($obj as &$value)
            keysToLower($value);
    return $obj;
}

您可以始终使用辅助栈轻松删除递归调用。 - Artefacto
我建议使用array_walk_recursive函数,但是我删除了我的帖子——我无法轻松地使它按照您的要求工作,尽管您可能想自己研究一下这个函数。 - Erik
显然,array_walk_recursive不会考虑使用回调函数创建的元素。 - tstenner
6个回答

5

Foreach使用一个内部副本进行遍历。尝试不使用它:

function keysToLower($obj)
{
    $type = (int) is_object($obj) - (int) is_array($obj);
    if ($type === 0) return $obj;
    reset($obj);
    while (($key = key($obj)) !== null)
    {
        $element = keysToLower(current($obj));
        switch ($type)
        {
        case 1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj->{$key});
                $key = $keyLowercase;
            }
            $obj->{$key} = $element;
            break;
        case -1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj[$key]);
                $key = $keyLowercase;
            }
            $obj[$key] = $element;
            break;
        }
        next($obj);
    }
    return $obj;
}

或者使用引用来避免使用副本:

function &keysToLower(&$obj)
{
    $type = (int) is_object($obj) - (int) is_array($obj);
    if ($type === 0) return $obj;
    foreach ($obj as $key => &$val)
    {
        $element = keysToLower($val);
        switch ($type)
        {
        case 1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj->{$key});
                $key = $keyLowercase;
            }
            $obj->{$key} = $element;
            break;
        case -1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj[$key]);
                $key = $keyLowercase;
            }
            $obj[$key] = $element;
            break;
        }
    }
    return $obj;
}

正常工作,但嵌套数组没有键时,第二层的每个数组都为空,例如keysToLower(array(array(array(1,2))))返回array(0=>array()) - tstenner
而且 if($key !== $keyLowercase) 防止处理数组中具有小写键的值,例如 array('lowercase'=>array('UPPERCASE'=>1)) 将无法工作。 插入 else $val=keysToLower($val); 可以解决这个问题。 - tstenner
通过将 if ($key !== $keyLowercase) 更改为 if ($key !== $keyLowercase && !ctype_digit($keyLowercase) 可以解决空数组的问题。 - tstenner
我刚刚检查了一下,它的运行速度比我的版本快了约30%,将is_int移动到switch语句中并先取消旧键会再次减慢它的速度。 - tstenner
在我的情况下,创建一个新对象而不是取消旧键可以加快速度。 - tstenner
显示剩余2条评论

3

@leoganda,你可以使用($obj)array_change_key_case((arr)$o)。 - gaborous

2

我想你并不关心转换为数组的类型...

function keys_to_lower($o) {
    if (is_object($o)) {
        $o = (array)$o;
    }
    if (is_array($o)) {
        return array_map('keys_to_lower', array_change_key_case($o));
    }
    else {
        return $o;
    }
}

我很在意,但我会先衡量一下,在返回之前是否进行这样的操作并重新构造会有任何不同。 - tstenner

2

这里是使用 lambda 的一个例子:

$multiArrayChangeKeyCase = function (&$array) use (&$multiArrayChangeKeyCase) {
    $array = array_change_key_case($array);

    foreach ($array as $key => $row)
        if (is_array($row))
             $multiArrayChangeKeyCase($array[$key]);
};

1
array_combine(array_map("strtolower", array_keys($a)), array_values($a))

1
不递归,但可能是加速他的代码的好起点。 - Erik
1
纠正:只要使用(array) $a进行转换,就可以处理数组。 - tstenner

0
一个有点晚的回复,针对一个旧的帖子,但是,有一个本地函数可以做到这一点,你可以按照以下方式进行封装。
function setKeyCasing($thing, $case = CASE_LOWER) {
    return array_change_key_case((array) $thing, $case);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接