PHP array_column 引发的一个小问题
32

array_column引发的一个问题

今天使用php数组array_column处理一个数据的时候,出现了一些小问题。

先简单的介绍下array_column.

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

array_column — 返回数组中指定的一列

array_column() 返回input数组中键值为column_key的列, 如果指定了可选参数index_key,那么input数组中的这一列的值将作为返回数组中对应值的键。

$items = array(
    [
        "uid"=>1,
        "pid"=>0,
        "views"=>100
    ],
    [
        "uid"=>2,
        "pid"=>1,
        "views"=>200
    ],
    [
        "uid"=>3,
        "pid"=>0,
        "views"=>300
    ],
    [
        "uid"=>4,
        "pid"=>0,
        "views"=>400
    ],
    [
        "uid"=>5,
        "pid"=>3,
        "views"=>500
    ]
);

array_column($items,'uid');[1,2,3,4,5];
array_column($items,'uid','view');//[100=>1,200=>2,300=>3,400=>4,500=>5];

上面的代码可以看到,这个数组可以帮助我们从一个多维数组中返回指定的key的数据。

有些时候,我们会配合array_search 查找多维数组的元素。

array_search('3',array_column($items,'uid'));//uid =3所在的数据

现在开始说下今天遇到的问题,今天有一个需求,需要统计上面数组中的数据,就是将pid 不等于的item 中的view 加到对应uid的views中去。举个例子,uid =1 的view是100.它有一个子元素uid =2 ,需要把uid =2 的view 加到uid =1 到views上。以此类推。这个需求首先想到的是遍历数组,然后查找每个item中的pid。如果不是0,然后用这个pid 当作uid。去查找pid所在的索引,然后将当前值加上去,然后删掉该记录。

代码如下

array_walk($items,function ($item,$key) use(&$items){
    $uid = $item['uid'];
    //当前item的pid
    $pid = $item['pid'];
    //父id的在items中的索引
    $pidIndex = array_search($pid,array_column($items,'uid'));
    if($pid !== 0 && false !== $pidIndex) {
        //sum到父id记录中
        $items[$pidIndex]['views'] += $item['views'];
        unset($items[$key]);
    }
});

初看,没有啥太大的问题。其实是有问题的。但是由于对array_column 太熟悉,通过Xdebug发现就会出现了问题。

因为array_column 返回的数组,是索引数组。而非关联。如果我们不删数据的话,上面代码是没问题的。但是我们删除了item数据,原来的items 就会变成关联数组了。

array_column($items,'uid');//[1,2,3,4,5]
$items = [[],[],[],[]];
unset($items[1]);
//items 变成
[0=>[],2=>[],3=>[]]

当我们再使用array_column 就拿不到对应的index了。

上面的代码可以修改成如下

array_walk($items,function ($item,$key) use(&$items){
    $uid = $item['uid'];
    $pid = $item['pid'];
    $row = array_combine(array_keys($items),array_column($items,'uid'));
    $pidIndex = array_search($pid,$row );
    if($pid !== 0 && false !== $pidIndex) {
        $items[$pidIndex]['views'] += $item['views'];
        unset($items[$key]);
    }
});

一般情况下,我们使用array_column 不会有啥问题。如果改变了里面的index,就会出问题。所以如果需要保持key不变的情况下。可以使用array_combinearray_keys

array_combine(array_keys($items),array_column($items,'uid'));

记录一下。希望以后遇到同样的问题的童鞋可以找到方案

闲云野鹤

本帖由系统于 2周前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 5
GhostCoder

好文 好文

2周前
Littlesqx

试了一下, 还是 foreach 真香

<?php
$items = array(
    [
        "uid"=>1,
        "pid"=>0,
        "views"=>100
    ],
    [
        "uid"=>2,
        "pid"=>1,
        "views"=>200
    ],
    [
        "uid"=>5,
        "pid"=>2,
        "views"=>500
    ],
    [
        "uid"=>3,
        "pid"=>0,
        "views"=>300
    ],
    [
        "uid"=>4,
        "pid"=>0,
        "views"=>400
    ],
    [
        "uid"=>5,
        "pid"=>3,
        "views"=>500
    ]
);

foreach ($items as &$item) {
    $pids = [$item['uid']]; // 可以求儿子的儿子
    foreach ($items as $k => $v) {
        if (in_array($v['pid'], $pids)) {
            $pids[] = $v['uid'];
            $item['views'] += $v['views'];
            unset($items[$k]);
        }
    }
}

var_dump($items);
2周前

问题的本质

其实关键是弄清楚 array_column() 返回的数组的下标为什么能和原来的二维数组一一对应、对一个数组使用 unset() 后发生了什么。

从 0 开始,连续编号

array_column() 用于从一个二维数组中取出某一列的元素,组成一个新的一维数组。当没有指定第三个参数时,array_column() 会将这些元素组成一个索引数组。显然,原本的二维数组有多少行,新的一维数组就有多少个元素,它们的下标刚好一一对应——它们的下标都是从 0 开始,连续编号。

$records = array(
    array(
        'id' => 2135,
        'first_name' => 'John',
        'last_name' => 'Doe'
    ),
    array(
        'id' => 3245,
        'first_name' => 'Sally',
        'last_name' => 'Smith'
    ),
    array(
        'id' => 5342,
        'first_name' => 'Jane',
        'last_name' => 'Jones'
    ),
    array(
        'id' => 5623,
        'first_name' => 'Peter',
        'last_name' => 'Doe'
    )
);

print_r($records);
print_r(array_column($records, 'id'));

file

unset() 的副作用

如果这个二维数组的下标不是一组连续的整数,那下标的这种对应关系就不复存在了。对一个索引数组使用 unset() 就会破坏下标的连续性。

$records = array(
    array(
        'id' => 2135,
        'first_name' => 'John',
        'last_name' => 'Doe'
    ),
    array(
        'id' => 3245,
        'first_name' => 'Sally',
        'last_name' => 'Smith'
    ),
    array(
        'id' => 5342,
        'first_name' => 'Jane',
        'last_name' => 'Jones'
    ),
    array(
        'id' => 5623,
        'first_name' => 'Peter',
        'last_name' => 'Doe'
    )
);

unset($records[1]);

print_r($records);
print_r(array_column($records, 'id'));

file

2周前
CismonX

这个解决方案不足的一点是,array_walk, array_column, array_search 都遍历了数组,因此这个操作为平方时间复杂度。

下面提供一个实现方案,可以在线性时间复杂度解决问题(为了简化代码,用到了我开发的collections扩展):

$partition = Collection::init($items)->partition(function ($value) {
    return $value['pid'];
});
$pid_views = Collection::init($partition->first)->associate(function ($value) {
    return new Pair($value['uid'], $value['views']);
});
$uid_items = Collection::init($partition->second)
    ->associateBy(function ($value) {
        return $value['uid'];
    })
    ->map(function ($value, $key) use ($pid_views) {
        $value['views'] += $pid_views->get($key, function ($value) {
            return 0;
        });
        return $value;
    });
//var_dump($uid_items->toArray());
2周前

1.在原来的基础上加个items=array_value(items) ,重新生成连续索引也可以,
2.另外如果$items 是索引数组的话 参见unset() 的副作用可以将unset($items[$key]);替换为array_splice($items,$key,1);

1周前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!