array_chunk()函数的妙用——分而治之

小激动

本来这篇博客应该是留到递归系列博客的第七诫写的,但是难以抑制自己第一次成功地用【分而治之】解决问题的小激动,以及怕到了那个时候忘了很多细节,就先写下这篇博客了。

前言:

前两天在学 N+1 问题的时,看到了解决 N+1 的问题的方法中出现了‘预加载’这个词,感觉这个词在学习 PDO 的时候遇见过,就去看了一下 PDO 相关的内容。 发现是记错了,把‘预处理’记成了‘预加载’。不过 PDO 这块好久没看了,刚好我想用原生php复现 N+1 问题,需要填充数据,就想着复习一下 PDO。不过 PDO 不是今天这篇博客的主题,主题是当我使用 PDO 的预处理实现一次性向数据库中添加多条数据时,我该怎么处理接收到的数据?

问题描述:

假如我想向topics表中一次性添加100条数据,接收到的数据如下:

$data = [
    ['id'=>1,'title'=>'sdf','body'=>'fdf','user_id'=>1,'category_id'=>3],
    ['id'=>2,'title'=>'gdf','body'=>'dfdf','user_id'=>6,'category_id'=>1],
    ...
    ['id'=>100,'title'=>'fgh','body'=>'hd','user_id'=>4,'category_id'=>1],
]
  1. 准备SQL语句:
    INSERT INTO topics(`title`,`body`,`user_id`,`category_id`) VALUES (?,?,?,?),(?,?,?,?),...,(?,?,?,?);
  2. 向表中添加数据:
    我接收到的是一个二维数组,但这并不是我想要的,我想要的是该数组的每个单元的array_values组成的一维数组,这样我才能一次性添加多条数据。什么意思呢?举个例子:
    假如我要向该表中添加两条数据,接收到的数据如下:
    $data = [
     ['title'=>'a','body'=>'b','user_id'=>2,'category_id'=>3],
     ['title'=>'c','body'=>'d','user_id'=>9,'category_id'=>8],
    ];
    我希望得到的数据如下:
    $insertData = ['a','b',2,3,'c','d',9,8];
    // 'a','b',2,3是$data第一个单元的array_values,'c','d',9,8是$data第二个单元的array_values。

问题建模:

输入是一个二维数组,数组的每个单元都是关联数组,输出是由每个单元的array_values组成的一维数组。示例如下:
输入:

[
    ['id'=>1,'name'=>'a'],
    ['id'=>2,'name'=>'b'],
    ['id'=>3,'name'=>'c'],
    ['id'=>4,'name'=>'d'],
]

输出:

[1,'a',2,'b',3,'c',4,'d']

具体实现:

1. 最简单的方法:

利用foreach遍历,或者根据我前面的博客中提到的递归前四诫,利用递归实现。但是时间复杂度有点感人,暂且不表。今天要讲的就是利用分而治之来解决这个问题。

2. 分而治之:

2.1 首先要问的是:能不能把这个大问题分解成若干个子问题来处理?
答案是可以的。例如:就算利用foreach遍历,处理规模为100的二维数组和处理规模为50的二维数组的方法也是一样的。
2.2 其次要问的是:该怎么把这个大问题拆解成若干个子问题?
答案是利用array_chunk()函数。

  • 语法:
    array_chunk(array $array, int $length, bool $preserve_keys = false): array
  • 参数:
    • array:需要操作的数组。
    • length:每个数组的单元数目。
  • 功能:
    将一个数组分割成多个数组,其中每个数组的单元数目由 length 决定。最后一个数组的单元数目可能会少于 length 个。

举个生活上的例子就好理解了:
想象一下,有5个球排成一行,每隔两个球放一块挡板,就会分成 2 2 1。【5个球】就是数组的长度,【每隔n个球放一块挡板】就是参数 length,在这里 length 为2。最后一堆球的个数为1,小于2,符合【最后一个数组的单元数目可能会少于 length 个】。
array_chunk 函数介绍完毕,就看看 array_chunk 在分而治之中的应用吧。
想要分而治之,只需要将输入的二维数组分成两个,然后再分别对其进行递归调用。
代码片段如下:

$re = array_chunk($data, ceil(count($data)/2));
$part1 = getArrValues($re[0]);
$part2 = getArrValues($re[1]);

注意:需要对count($data)/2)进行向上取整,即ceil(count($data)/2))
就拿php文档中的例子来说吧:
输入:$input_array = array(‘a’, ‘b’, ‘c’, ‘d’, ‘e’);
如果向下取整:

print_r(array_chunk($input_array, floor(count($input_array)/2)));
// 输出:[[0]=>['a','b'],[1]=>['c','d'],[3]=>['e']]
分析:$length = floor(count($input_array)/2))) = 2; 五个球,每隔两个球放一块挡板,就会分成三堆。所以分成了三个数组。

向上取整得到的输出如下:

print_r(array_chunk($input_array, ceil(count($input_array)/2)));
[[0]=>['a','b','c'],[1]=>['d','e']]
分析:$length =  ceil(count($input_array)/2))) = 3;
五个球,每隔三个球放一块挡板,就会分成两堆。所以就得到了两个数组。

2.3 最后要问的是:什么时候终止递归?换句话说:我们要处理的最简单的问题是什么?
最简单的问题就是输入的二维数组中只有一个单元。
例如:

$data = [
    ['id'=>1,'name'='a']
]

怎么处理?

if (count($data)==1) {
    return array_values($data[0]);
}

分析到这里,基本没什么问题了,再结合递归十诫之第二诫:使用array_merge函数来构建数组,就能写出完整的代码了。
完整代码如下:

function getArrValues($data)
{
    if (count($data)==1) {
        return array_values($data[0]);
    }
    $re = array_chunk($data, ceil(count($data)/2));
    $part1 = getArrValues($re[0]);
    $part2 = getArrValues($re[1]);
    return array_merge($part1, $part2);
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 3
Junwind

file 这样也倒是简单,不知道和你说的方法比较的速度如何

2年前 评论
Moonshadow2333 (楼主) 2年前

楼主 50-70w数据要怎么处理,老是内存溢出‘..’

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!