最近跟二进制字符串杠上了,总是喜欢对二进制字符串存储和运算进行研究。今天又来写一个使用场景的代码。
使用场景:在项目中需要存储一个对象被租用的时间段,以每个小时作为一个时间段,从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,改成了把每个小时时间点作为元素,很快,一个循环就把我们的目标效果实现了。