大话业务场景与解决方案:数据分散周期筛选
背景:
用户上传的宝宝相册记录,一个记录可以包含 N 个照片,产品期望给用户输出影集,如:一周/宝宝百天/宝宝生日的影集
用户同一天可能会有多个记录
简化需求内容,产品期望可以将数据按周期筛选出并分散的到日期,相同记录 ID 下,再分散到每个照片
如:将最近一周的数据,按天分组,然后分散到每个记录 ID 下,每个记录 ID 都尽可能覆盖到获取照片,最终筛选 N 条数据
如:将最近 100 天的数据,按周分组,分散到每个周的日期数据,然后分散到每个记录 ID 下,每个记录 ID 都尽可能覆盖到获取照片,最终筛选 N 条数据
如:将一年的数据,按月分组,分散到每个月份的日期数据,然后分散到每个记录 ID 下,每个记录 ID 都尽可能覆盖到获取照片,最终筛选 N 条数据
结合实例说明下:
取照片顺序:
05-01->ID_1->1.jpg
05-02->ID_4->7.jpg
05-03->ID_5->9.jpg
05-01->ID_2->3.jpg
05-02->ID_4->8.jpg
05-03->ID_5->10.jpg
05-01->ID_3->6.jpg
05-03->ID_6->11.jpg
05-01->ID_1->2.jpg
05-03->ID_6->12.jpg
05-01->ID_2->4.jpg
05-03->ID_6->13.jpg
05-01->ID_2->5.jpg
方案
分析需求,抽离出本质要解决的问题,其实就是根据周期(天,周,月),然后再遍历每个周围下的记录 ID,每个记录 ID 的照片都可以尽可能的覆盖到,继续遍历下一个周期下的记录 ID,直到获得 N 条数据/没数据,就结束
最初的一个解决方案,就是将数据进行按周期分组,然后遍历每个周期,取出来周期中的日期记录数据,然后将它移除,继续遍历下一个周期日期记录,直到遍历完到 15 条/没数据结束
后来发现实现起来相当的吃力,而且逻辑又有些复杂
最后发现 PHP 数组支持内部指针,基于内置指针特性,将数据按周期分组后,通过内容指针,实现每次取数据的时候偏移位置,最终实现的通用的函数封装
内部指针:
<?php
current( &$arr ) 每个数组的当前单元,初始值的 数组的第一个单元
next ( &$arr ) 返回数组中的下一个单元,如果没值则返回falsh
prev ( &$arr ) 返回数组中的上一个单元,如果没有值则返回true
end ( &$arr ) 将内部指针移动到最后一个单元,并返回其值
reset ( &$arr ) 将内部指针移动到第一个单元,如果没值,则返回falsh
each ( &$arr ) 返回数组中当前的键/值,对并将数组指针,向前移动一步
逻辑:
先获取当前数组指针值 current
如果没有数据的时候尝试 reset,重置将内部指针移动到第一个单元
通过 array_shift 获取照片数据
没数据的时候 unset,继续遍历
最后将指针位移 next,等下一次来获取
核心代码如下:
<?php
/**
* 获取分组下的素材
* @param array $periodGroups 周期分组
* @param array $selAlbums 被选中素材album数组
* @param array $log
* @param int $max
* @return bool
*/
public function getPeriodMaterial(&$periodGroups, &$selAlbums = [], &$log = [], $max = 15)
{
if (empty($periodGroups)) return false;
foreach ($periodGroups as $period => &$dates) {
//满足素材数量,out
if ($this->getSelAllNums($selAlbums) >= $max) return false;
if (empty($dates)) continue;
//数组指针 - 日期关联数组
$dateTarget = current($dates);
//如果没有日期目标,重置数组指针,再尝试获取
if (empty($dateTarget)) {
reset($dates);
$dateTarget = current($dates);
}
//还是不存在日期下的记录,直接删除日期分组
if (empty($dateTarget)) {
$dates = [];
continue;
}
$date = key($dates);
next($dates);
//获取日期下的所有记录
$records = &$dates[$date];
//数组指针 - 记录数组
$recordTarget = current($records);
if (empty($recordTarget)) {
reset($records);
$recordTarget = current($records);
}
if (empty($recordTarget)) {
unset($dates[$date]);
continue;
}
$recordIndex = key($records);
next($records);
//获取记录
/** @var MBabyRecord $mRecord */
$mRecord = &$records[$recordIndex];
/** @var array $recordDetail */
$recordDetail = &$mRecord->record_detail;
$mAlbum = array_shift($recordDetail);
$selAlbums[$date][$mAlbum->record_id][] = $mAlbum;
$log[] = [
'period_num' => $period,
'date' => $date,
'record_id' => empty($mRecord->id) ? 0 : $mRecord->id,
'album_id' => empty($mAlbum->id) ? 0 : $mAlbum->id
];
if (empty($mRecord->record_detail)) {
unset($records[$recordIndex]);
if (empty($records)) unset($dates[$date]);
if (empty($dates)) $dates = [];
continue;
}
}
//清洗删除已经没有日期记录的分组
foreach ($periodGroups as $period => $item) {
if (empty($item)) unset($periodGroups[$period]);
}
return true;
}
百天数据,按:周数,分组
<?php
/**
* 根据周期分组,百天根据周分组
* @param MBabyRecord[] $records
* @return array
*/
public function groupByPeriod($records)
{
if (empty($records)) return [];
$groups = [];
foreach ($records as $item) {
$recordDate = $item->record_date;
$week = Carbon::parse($recordDate)->weekOfYear;
$groups[$week][$item->record_date][] = $item;
}
return $groups;
}
周岁数据,按:年-月份数,分组
<?php
/**
* 根据周分组
* @param MBabyRecord[] $records
* @return array
*/
public function groupByPeriod($records)
{
if (empty($records)) return [];
$groups = [];
foreach ($records as $item) {
if (empty($item->record_date)) continue;
$recordDate = Carbon::parse($item->record_date);
$year = $recordDate->year;
$month = $recordDate->month;
$groups[$year . '-' . $month][$item->record_date][] = $item;
}
return $groups;
}