返回

PHP二维数组提取指定键值对:实例与最佳实践(54字)

php

PHP 从二维数组提取键值对,生成关联数组

咱们在用 PHP 处理数据的时候,经常会碰到一种情况:手里有个二维数组,比如从数据库查出来的一堆结果,每行数据本身又是个小数组(或者对象)。现在想把它转换成一个一维的关联数组,用其中一列的值当做新数组的键(Key),另一列的值当做新数组的值(Value)。

举个栗子,假设我们有这么个数据:

$resultSet = [
    ['id' => 101, 'name' => '张三', 'email' => 'zhangsan@example.com'],
    ['id' => 102, 'name' => '李四', 'email' => 'lisi@example.com'],
    ['id' => 105, 'name' => '王五', 'email' => 'wangwu@example.com']
];

我们想要的结果是,用 id 做键,name 做值,变成下面这样:

$desiredArray = [
    101 => '张三',
    102 => '李四',
    105 => '王五'
];

或者用 id 做键,email 做值:

$desiredArray = [
    101 => 'zhangsan@example.com',
    102 => 'lisi@example.com',
    105 => 'wangwu@example.com'
];

刚开始接触 PHP 的朋友可能会像提问者那样尝试,写出类似这样的代码:

// 错误的尝试
$dataarray = [];
foreach ($resultSet as $row) {
    // 这行语法是错的!
    $dataarray[] = $row['id'] => $row['name']; 
}

结果发现,这段代码根本跑不起来,会报语法错误。

问题出在哪儿?

上面那段错误代码的问题在于对 PHP 数组赋值语法的理解。

$dataarray[] = $value; 这个写法,是 PHP 里往数组 末尾 添加一个 新元素 的快捷方式。PHP 会自动给这个新元素分配一个数字索引(通常是从 0 开始,依次递增)。

$key => $value 这个胖箭头 (=>) 语法,是用在以下两种场景的:

  1. 在定义数组时 ,直接指定键值对,像这样:
    $myArray = [
        'fruit' => 'apple',
        'count' => 5
    ];
    
  2. 直接给数组的某个特定键赋值 ,像这样:
    $myArray = [];
    $myKey = 'fruit';
    $myValue = 'banana';
    $myArray[$myKey] = $myValue; // $myArray 现在是 ['fruit' => 'banana']
    

错误代码尝试把这两种语法混在一起,想在用 [] 追加元素的同时,通过 => 指定键名,这是 PHP 语法不允许的。它会认为你尝试把一个 ($row['id'] => $row['name']) 表达式的结果(这本身就不是一个有效的 PHP 表达式能产生的值)赋给 $dataarray 的下一个数字索引,这自然就错了。

解决方案

知道了问题所在,解决起来就不难了。有几种常见且有效的方法可以实现我们的目标。

方案一:老老实实循环赋值

这是最直观,也是最基础的方法。直接用 foreach 循环遍历原始的二维数组,然后在循环体内部,明确地把需要的列赋值给新数组的对应键。

原理与作用:

这种方法模拟了我们手动处理数据的过程。遍历每一行数据,拿出需要的“键列”的值和“值列”的值,然后在新数组里,以“键列的值”为键,“值列的值”为值,建立一一对应的关系。

代码示例:

$resultSet = [
    ['id' => 101, 'name' => '张三', 'email' => 'zhangsan@example.com'],
    ['id' => 102, 'name' => '李四', 'email' => 'lisi@example.com'],
    ['id' => 105, 'name' => '王五', 'email' => 'wangwu@example.com']
];

$idToNameArray = []; // 初始化一个空数组

foreach ($resultSet as $row) {
    // $row['id'] 的值作为新数组的键
    // $row['name'] 的值作为新数组的值
    $idToNameArray[$row['id']] = $row['name']; 
}

print_r($idToNameArray);
// 输出:
// Array
// (
//     [101] => 张三
//     [102] => 李四
//     [105] => 王五
// )

// 如果想要 id => email 的映射
$idToEmailArray = [];
foreach ($resultSet as $row) {
    $idToEmailArray[$row['id']] = $row['email'];
}
print_r($idToEmailArray);
// 输出:
// Array
// (
//     [101] => zhangsan@example.com
//     [102] => lisi@example.com
//     [105] => wangwu@example.com
// )

说明:

这种方法简单易懂,对 PHP 版本没啥特殊要求。而且,如果在循环里还需要对数据做一些额外的判断或处理,这种方式非常灵活。

注意事项与进阶:

  1. 键的唯一性: 这种方法(以及后面要讲的 array_column)默认假设你用来做键的那一列 (id) 的值是唯一的。如果原始数据里有重复的 id,后面的数据会覆盖掉前面相同 id 的数据。比如,如果有两条记录的 id 都是 101,那么最终 $idToNameArray[101] 的值会是 最后 遍历到的那条记录的 name
  2. 处理重复键: 如果你需要处理重复键的情况,比如把所有同 idname 收集起来,循环方法可以很方便地修改:
    $resultSetWithDuplicates = [
        ['id' => 101, 'name' => '张三'],
        ['id' => 102, 'name' => '李四'],
        ['id' => 101, 'name' => '张三丰'], // 重复的 ID
    ];
    
    $idToNamesArray = [];
    foreach ($resultSetWithDuplicates as $row) {
        $key = $row['id'];
        $value = $row['name'];
    
        if (!isset($idToNamesArray[$key])) {
            // 如果这个 key 还没出现过,直接赋值(或者赋值为数组)
            $idToNamesArray[$key] = $value; // 或者 $idToNamesArray[$key] = [$value]; 如果想统一存数组
        } else {
            // 如果 key 已经存在了
            if (!is_array($idToNamesArray[$key])) {
                // 如果之前存的不是数组,把它变成数组
                $idToNamesArray[$key] = [$idToNamesArray[$key]]; 
            }
            // 把当前的 value 追加进去
            $idToNamesArray[$key][] = $value;
        }
    }
    print_r($idToNamesArray);
    // 输出类似:
    // Array
    // (
    //     [101] => Array
    //         (
    //             [0] => 张三
    //             [1] => 张三丰
    //         )
    //     [102] => 李四
    // )
    

方案二:使用 array_column() 函数 - 官方推荐,一行搞定

PHP 提供了一个非常方便的内置函数 array_column(),专门用来从多维数组中取出某一列的值。更妙的是,从 PHP 5.5.0 开始,这个函数可以接受第三个参数,直接指定用哪一列作为结果数组的键!这简直是为我们这个问题量身定做的。

原理与作用:

array_column(array $input, mixed $column_key, mixed $index_key = null): array

  • $input: 原始的二维数组。
  • $column_key: 你想要作为新数组 的那一列的键名(比如 'name''email')。如果你希望保留整行作为值,可以传 null
  • $index_key: (可选) 你想要作为新数组 的那一列的键名(比如 'id')。

当你提供了第三个参数 $index_key 时,array_column 就会按照你的要求,用 $index_key 对应的列的值作为键,$column_key 对应的列的值作为值,生成目标关联数组。

代码示例:

$resultSet = [
    ['id' => 101, 'name' => '张三', 'email' => 'zhangsan@example.com'],
    ['id' => 102, 'name' => '李四', 'email' => 'lisi@example.com'],
    ['id' => 105, 'name' => '王五', 'email' => 'wangwu@example.com']
];

// 使用 id 做键,name 做值
$idToNameArray = array_column($resultSet, 'name', 'id');
print_r($idToNameArray);
// 输出:
// Array
// (
//     [101] => 张三
//     [102] => 李四
//     [105] => 王五
// )

// 使用 id 做键,email 做值
$idToEmailArray = array_column($resultSet, 'email', 'id');
print_r($idToEmailArray);
// 输出:
// Array
// (
//     [101] => zhangsan@example.com
//     [102] => lisi@example.com
//     [105] => wangwu@example.com
// )

// 如果你想用 id 做键,整行数据(子数组)做值
$idToRowArray = array_column($resultSet, null, 'id');
print_r($idToRowArray);
// 输出:
// Array
// (
//     [101] => Array
//         (
//             [id] => 101
//             [name] => 张三
//             [email] => zhangsan@example.com
//         )
//     [102] => Array
//         (
//             [id] => 102
//             [name] => 李四
//             [email] => lisi@example.com
//         )
//     [105] => Array
//         (
//             [id] => 105
//             [name] => 王五
//             [email] => wangwu@example.com
//         )
// )

说明:

array_column() 是完成这项任务最简洁、最高效的方式(通常底层是 C 实现,比纯 PHP 循环快)。代码可读性也很好,一看就知道意图。只要你的 PHP 版本 >= 5.5.0,强烈推荐使用这个方法。

注意事项与进阶:

  1. PHP 版本: 确保你的 PHP 环境是 5.5.0 或更高版本。
  2. 键的唯一性: 和循环方法一样,如果用作键的那一列 ($index_key) 存在重复值,array_column 同样会用后面遇到的值覆盖前面的。它不提供内置的处理重复键的逻辑。如果你需要像上面循环例子那样聚合重复键的数据,array_column 帮不了你,还得用循环或其他方法。
  3. 键的类型: PHP 的数组键可以是整数或字符串。如果你的 $index_key 列包含 null 或者布尔值,它们会被转换成空字符串 '' 或整数 0/1 作为键。如果包含浮点数,小数部分会被截断转换成整数。如果包含对象或数组,则不能直接作为键,可能会报错(取决于对象是否实现了特定接口或 __toString 方法)。通常用作 ID 的列都是整数或字符串,问题不大,但需要注意。
  4. 性能: 对于大规模数据集,array_column 通常是性能最优的选择。

方案三:使用 array_reduce() 函数 - 函数式编程思路

如果你喜欢函数式编程风格,或者需要在转换过程中执行更复杂的逻辑,array_reduce() 也可以实现这个目标。

原理与作用:

array_reduce(array $array, callable $callback, mixed $initial = null): mixed

array_reduce 的作用是对数组中的每个元素依次执行一个回调函数 ($callback),并将上次回调的返回值(或者初始值 $initial)传递给下次回调,最终将数组“缩减”为一个单一的值。我们可以利用这个过程,把初始值设为一个空数组 [],然后在每次回调中,把当前行的键值对添加到这个累积的数组中。

代码示例:

$resultSet = [
    ['id' => 101, 'name' => '张三', 'email' => 'zhangsan@example.com'],
    ['id' => 102, 'name' => '李四', 'email' => 'lisi@example.com'],
    ['id' => 105, 'name' => '王五', 'email' => 'wangwu@example.com']
];

// 使用 id 做键,name 做值
$idToNameArray = array_reduce($resultSet, function ($carry, $item) {
    // $carry 是上一次回调返回的累积结果(初始是 [])
    // $item 是当前正在处理的行(子数组)
    $carry[$item['id']] = $item['name']; // 将 id=>name 添加到结果数组
    return $carry; // 返回更新后的结果数组,供下一次迭代使用
}, []); // 初始值是一个空数组

print_r($idToNameArray);
// 输出:
// Array
// (
//     [101] => 张三
//     [102] => 李四
//     [105] => 王五
// )

// 使用 id 做键,email 做值
$idToEmailArray = array_reduce($resultSet, function ($carry, $item) {
    $carry[$item['id']] = $item['email'];
    return $carry;
}, []);

print_r($idToEmailArray);
// 输出:
// Array
// (
//     [101] => zhangsan@example.com
//     [102] => lisi@example.com
//     [105] => wangwu@example.com
// )

说明:

array_reduce 提供了一种不同的思考方式。虽然对于这个特定问题,代码量比 array_column 多,但它的回调函数给了你完全的控制权,可以在每次迭代中执行非常复杂的逻辑,而不仅仅是简单的键值提取。

注意事项与进阶:

  1. 理解回调: array_reduce 的关键在于理解 $carry$item 的作用以及 $initial 的重要性。 $carry 在每次调用间传递状态,$item 代表当前元素。
  2. 性能: array_reduce 通常比 array_column 慢,因为它涉及在 PHP 层面重复调用回调函数。但它比手写循环在某些情况下可能因为函数调用的开销而略慢或相当。对于性能敏感的大数据集,还是优先考虑 array_column
  3. 灵活性: 它的强项在于灵活性。如果你需要在转换时进行条件判断、数据格式化、或者像处理重复键那样聚合数据,array_reduce 的回调函数内部可以轻松实现这些逻辑。

总结一下

要把一个包含多行数据的二维数组(像是数据库结果集),转换成用某一列作键、另一列作值的一维关联数组,你有以下几种主要方法:

  1. foreach 循环 + 手动赋值: 最基础、最通用,易于理解和修改以适应复杂逻辑(如处理重复键),兼容所有 PHP 版本。
  2. array_column($array, 'value_column', 'key_column') (PHP >= 5.5.0) 最简洁、最高效的内置函数解决方案,代码可读性强,是解决这个特定问题的首选。但处理不了重复键的特殊聚合。
  3. array_reduce() 函数式编程的选择,非常灵活,允许在转换过程中加入复杂逻辑,但代码相对啰嗦,性能通常不如 array_column

根据你的具体需求、PHP 版本以及对代码简洁性、性能、灵活性的侧重,选择最适合你的方法就行了。对于最开始那个简单的键值提取需求,array_column() 无疑是最佳实践。