用二进制字符串作为索引,构建时间段

394 查看

最近跟二进制字符串杠上了,总是喜欢对二进制字符串存储和运算进行研究。今天又来写一个使用场景的代码。

使用场景:在项目中需要存储一个对象被租用的时间段,以每个小时作为一个时间段,从8:00到晚上20:00,一共12个时间段;现在按照二进制字符串的形式存储每个时间段被租用的情况,如果该时段被租用,就在对应的位置上用1表示,未租用就用0表示,例如 001100000100 表示10:00-12:00,17:00-18:00这两个时间段被租用,并将该二进制字符串存储在数据库中;现在,在读取数据时,需要将二进制字符串还原为时间段,(如果出现连续的时间段时,需合并为一个跨多个小时的时间段),写一个函数来实现。

在面对这个问题的时候,我想了很多,因为我们要从便捷性和效率两个角度去思考,如果仅仅是为了实现便捷性,那么通过拆分数组,遍历,一定能很快实现。于是,我写了下面的函数:

function bin_string_to_hours($string,$hours = array('08:00-09:00','09:00-10:00','10:00-11:00','11:00-12:00','12:00-13:00','13:00-14:00','14:00-15:00','15:00-16:00','16:00-17:00','17:00-18:00','18:00-19:00','19:00-20:00'))
{
    // 检查$string格式,必须是12位仅含01的字符串
    if(!preg_match("/^[0|1]{12}$/",$string)) return false;
    // 找出对应的位置,通过unset,最终余留的$hours元素就是已选中的几个值,非选中元素被剔除了
    $arr = str_split($string);
    foreach($arr as $key => $value) {
        if($value == 0) unset($hours[$key]);
    }
    // 如果所有的时段都没有被订
    if(empty($hours)) return null;
    // 利用剩下的这个$hours进行字符串处理
    $hours = array_values($hours);
    $result = array();
    list($start, $last) = explode('-', $hours[0]);
    for($i = 1, $n = count($hours); $i < $n; $i++) {
        list($start2, $last2) = explode('-', $hours[$i]);
        if ($start2 != $last) {
            $result[] = $start . '-' . $last;
            $start = $start2;
            $last = $last2;
        } else {
            $last = $last2;
        }
    }
    $result[] = $start . '-' . $last;
    unset($hours);
    return implode(',', $result);
}

用两个遍历,解决了这个问题。但是,有没有更烧脑的解决办法呢?我又做了下面的修改。

function bin_string_to_hours($string,$hours = array('08:00-09:00','09:00-10:00','10:00-11:00','11:00-12:00','12:00-13:00','13:00-14:00','14:00-15:00','15:00-16:00','16:00-17:00','17:00-18:00','18:00-19:00','19:00-20:00'))
{
    // 检查$string格式,必须是12位仅含01的字符串
    if(!preg_match("/^[0|1]{12}$/",$string)) return false;
    // 通过对字符串进行数组切割,然后通过循环遍历,找出所有1,并将连续的(或单个的)1分为一个组
    $i = 0;
    $groups = array();
    $arr = str_split($string);
    foreach($arr as $key => $value) {
        if(isset($arr[$key - 1]) && $arr[$key - 1] == $value) // 如果上一个元素与当前元素相等(同为1或0),不进行任何操作
        {}
        else // 如果当前元素与上一个元素不同(一个为0,一个为1),那么新建一个组
        {
            $i ++;
        }
        if($value == 0) // 如果该值为0,这个组别就没有存在的必要了
        {
            unset($groups[$i]);
            continue;
        }
        $groups[$i][] = $key; // 把对应的索引存入到组中
    }
    // 从分组中取出对应的索引,在按每组去对字符串进行处理:该组的第一个索引对应的字串的前面部分,和最后一个索引对应的字串的后面部分,就是时间的开始与截止
    $return = array();
    foreach($groups as $group) {
        $index_start = current($group);
        if(count($group) == 1) // 如果这个组只有一个元素,也就是只有一个时间段,直接返回这个时间段的值即可
        {
            $return[] = $hours[$index_start];
            continue;
        }
        $index_end = end($group);
        $string_start = current(explode('-',$hours[$index_start]));
        $string_end = end(explode('-',$hours[$index_end]));
        $return[] = $string_start.'-'.$string_end;
    }
    $return = implode('-',$return);
    return $return;
}

通过分组构建分组数组的方法来解决,如果数据量大的时候,明显这种方法更加节省资源。可是,当我和小伙伴儿再继续烧脑的时候,在马上把脑袋烤熟的一瞬间,我们发现了只需要一次遍历就可以解决问题的办法。

function bin_string_to_hours($string,$hours = array('08:00','09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00','17:00','18:00','19:00','20:00'))
{
    if(!preg_match("/^[0|1]{12}$/",$string)) return false; // 检查$string格式,必须是12位仅含01的字符串

    $result = array();
    $index_start = 0; // 用来记录连续的1最前的索引
    $index_end = 0; // 用来记录连续的1最末的索引
    $count = 0; // 用来记录有多少个连续的1
    for($i = 0; $i <= 12; $i ++)
    {
        if($i < 12 && $string[$i] == '1') // string最大索引为11,所以这里必须<12
        {
            if($count == 0) $index_start = $i;
            $index_end = $i + 1;
            $count ++;
        }
        else
        {
            if($count > 0 && $index_end - $index_start == $count) // count必须>0的情况下,在能进行字符串组合,为0就没有意义了
                $result[] = $hours[$index_start].'~'.$hours[$index_end];
            $count = 0;
        }
    }

    return implode(',',$result);
}

这个算法里面,把上面原本用时间段作为元素的$hours,改成了把每个小时时间点作为元素,很快,一个循环就把我们的目标效果实现了。