目录

大话业务场景与解决方案:数据分散周期筛选

目录

背景:

用户上传的宝宝相册记录,一个记录可以包含 N 个照片,产品期望给用户输出影集,如:一周/宝宝百天/宝宝生日的影集

用户同一天可能会有多个记录

简化需求内容,产品期望可以将数据按周期筛选出并分散的到日期,相同记录 ID 下,再分散到每个照片


如:将最近一周的数据,按天分组,然后分散到每个记录 ID 下,每个记录 ID 都尽可能覆盖到获取照片,最终筛选 N 条数据

如:将最近 100 天的数据,按周分组,分散到每个周的日期数据,然后分散到每个记录 ID 下,每个记录 ID 都尽可能覆盖到获取照片,最终筛选 N 条数据

如:将一年的数据,按月分组,分散到每个月份的日期数据,然后分散到每个记录 ID 下,每个记录 ID 都尽可能覆盖到获取照片,最终筛选 N 条数据

结合实例说明下: /imgs/date-data.jpg?v=1222

取照片顺序:


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;
}