【问题标题】:Algorithm to combine / merge date ranges组合/合并日期范围的算法
【发布时间】:2011-06-25 17:53:43
【问题描述】:

我正在尝试找到如何将日期范围合并到一个数据库记录(数组元素)中的最佳方法。

这是我拥有的数据:

  Array
(
    [0] => Array
        (
            [id] => 18298
            [start_date] => 2011-07-09
            [end_date] => 2011-10-01
        )

    [1] => Array
        (
            [id] => 18297
            [start_date] => 2011-06-01
            [end_date] => 2011-06-30
        )

    [2] => Array
        (
            [id] => 17113
            [start_date] => 2011-03-31
            [end_date] => 2011-05-31
        )

    [3] => Array
        (
            [id] => 20555
            [start_date] => 2011-01-03
            [end_date] => 2011-03-31
        )
)

在我们组合它们之后,数组(或数据库)应该是这样的:

Array
(
    [0] => Array
        (
            [merged_ids] => 18298
            [start_date] => 2011-07-09
            [end_date] => 2011-10-01
        )

    [1] => Array
        (
            [merged_ids] => 18297, 17113, 20555
            [start_date] => 2011-01-03
            [end_date] => 2011-06-30
        )
)

是否有任何算法可以遍历所有元素/范围并将它们组合起来?哪种方式更好/更容易 - 通过数据库 (MYSQL) 或编码 (PHP)?

非常感谢任何建议。

谢谢!

更新:抱歉,我没有提供足够的信息:我们应该合并任何连续和重叠的日期范围。

【问题讨论】:

  • 如何确定应该合并哪些日期?如果它们是连续的吗?
  • 是否保证原始日期范围不会重叠?
  • 在他给出的示例数据中它们不是重叠的吗? 2055517113 重叠。另外,merged_ids 属性是否对其元素有任何排序顺序?
  • @Alnitak & @Argote,我们应该合并任何连续和重叠的日期范围

标签: php algorithm merge date-range


【解决方案1】:

按开始日期排序。

然后遍历并检查下一项的开始日期是在当前结束日期之前还是之后。如果是,则将下一个合并到当前的。然后继续。

【讨论】:

  • 您好@Amber,感谢您的回答。我忘了提到日期范围也可能重叠。这些也应该合并。
  • @Kelvin - 这很简单 - 只需检查下一个日期范围的 end 日期是否早于当前日期范围的结束日期。
  • @Kelvin - 实际上,你甚至不需要这样做 - 因为如果结束日期在当前结束日期之前,开始日期也肯定在当前结束日期之前,所以我描述的原始检查也将捕获它。
  • 这是该逻辑的 PHP 代码:mdb-blog.blogspot.com/2020/12/…
  • 此合并逻辑的 PHP 代码 - 可在此处找到:mdb-blog.blogspot.com/2020/12/…
【解决方案2】:

我编写了组合/合并范围列表的函数。它是用 Python 编写的,但用 PHP 重写应该很容易。这是完整的代码:https://gist.github.com/barszczmm/8447665,这是简化的算法(仍在 Python 中):

list_of_ranges.sort() # sort input list
new_list_of_ranges = [] # output list

new_range_item_start = None
new_range_item_end = None

length = len(list_of_ranges)
for i, range_item in enumerate(list_of_ranges):
    if new_range_item_start is None:
        new_range_item_start = range_item[0]
        new_range_item_end = range_item[1]
    elif new_range_item_end >= range_item[0]:
        new_range_item_end = max(range_item[1], new_range_item_end)
    else:
        new_list_of_ranges.append((new_range_item_start, new_range_item_end))
        new_range_item_start = range_item[0]
        new_range_item_end = range_item[1]
    # save if this is last item
    if i + 1 == length:
        new_list_of_ranges.append((new_range_item_start, new_range_item_end))

【讨论】:

    【解决方案3】:

    实现是这样的:

    function mergeDateTimeRanges($ranges)
    {
        $retVal = [];
        //sort date ranges by begin time
        usort($ranges, function ($a, $b) {
            return strcmp($a['begin_at'], $b['begin_at']);
        });
    
        $currentRange = [];
        foreach ($ranges as $range) {
            // bypass invalid value
            if ($range['begin_at'] >= $range['end_at']) {
                continue;
            }
            //fill in the first element
            if (empty($currentRange)) {
                $currentRange = $range;
                continue;
            }
    
            if ($currentRange['end_at'] < $range['begin_at']) {
                $retVal[] = $currentRange;
                $currentRange = $range;
            } elseif ($currentRange['end_at'] < $range['end_at']) {
                $currentRange ['end_at'] = $range['end_at'];
            }
        }
    
        if ($currentRange) {
            $retVal[] = $currentRange;
        }
    
        return $retVal;
    }
    

    【讨论】:

    • 这个答案没有提供预期的输出。
    【解决方案4】:

    我的方法将生成一个合并数组,其中重叠或连续的日期被组合在一起,组的 id 被存储为逗号分隔值的字符串。

    我正在使用现代“宇宙飞船操作员”(&lt;=&gt;)进行usort()s 比较。如果你的代码在php7以下的版本上运行,你可以使用:

    usort($array,function($a,$b){ return strcmp($a['start_date'],$b['start_date']); });
    

    请参阅内联 cmets 了解分步说明。

    代码:(Demo)

    function mergeRanges($array){
        usort($array,function($a,$b){ return $a['start_date']<=>$b['start_date']; });  // order by start_date ASC
    
        foreach($array as $i=>$row){
             if($i && $row['start_date']<=date('Y-m-d',strtotime("{$result[$x]['end_date']} +1 day"))){  // not the first iteration and dates are within current group's range
                if($row['end_date']>$result[$x]['end_date']){  // only if current end_date is greater than existing end_date
                    $result[$x]['end_date']=$row['end_date'];  // overwrite end_date with new end_date in group
                }
                $result[$x]['merged_ids'][]=$row['id'];  // append id to merged_ids subarray
            }else{  // first iteration or out of range; start new group
                if($i){  // if not first iteration
                    $result[$x]['merged_ids']=implode(', ',$result[$x]['merged_ids']);  // convert previous group's id elements to csv string
                }else{  // first iteration
                    $x=-1;  // declare $x as -1 so that it becomes 0 when incremented with ++$x
                }
                $result[++$x]=['merged_ids'=>[$row['id']],'start_date'=>$row['start_date'],'end_date'=>$row['end_date']]; // declare new group
            }
        }
        $result[$x]['merged_ids']=implode(', ',$result[$x]['merged_ids']);  // convert final merged_ids subarray to csv string
        return $result;
    }
    
    $array=[
        ['id'=>18298,'start_date'=>'2011-07-09','end_date'=>'2011-10-01'],
        ['id'=>18297,'start_date'=>'2011-06-01','end_date'=>'2011-06-30'],
        ['id'=>17113,'start_date'=>'2011-03-31','end_date'=>'2011-05-31'],  // tests that 17113 and 18297 belong in same group
        ['id'=>20556,'start_date'=>'2011-02-03','end_date'=>'2011-02-13'],  // tests that "fully overlapped" date range is included
        ['id'=>20555,'start_date'=>'2011-01-03','end_date'=>'2011-03-31']
    ];
    
    print_r(mergeRanges($array));
    

    输出:

    Array
    (
        [0] => Array
            (
                [merged_ids] => 20555, 20556, 17113, 18297
                [start_date] => 2011-01-03
                [end_date] => 2011-06-30
            )
    
        [1] => Array
            (
                [merged_ids] => 18298
                [start_date] => 2011-07-09
                [end_date] => 2011-10-01
            )
    
    )
    

    【讨论】:

      猜你喜欢
      • 2017-02-09
      • 1970-01-01
      • 2014-10-13
      • 1970-01-01
      • 2013-03-24
      • 1970-01-01
      • 2016-12-27
      • 2016-08-22
      • 1970-01-01
      相关资源
      最近更新 更多