Имеется массив дат с подмассивами из времён. Например:
Array
(
[2020-02-07] => Array
(
[09:00] => 1500
[10:30] => 1500
[11:00] => 1500
[11:30] => 1500
)
[2020-02-08] => Array
(
[09:00] => 1500
[09:30] => 1500
[11:00] => 1500
)
)
Необходимо сформировать новый массив, но при следующем условии: если разница между временем (то, что в ключах) равна 30 минут то необходимо создать из этих дат диапазон (например, подряд идут даты: 8:00, 8:30, 9:00, 9:30 то это будет 8:00 - 9:30) и записать в новый массив дату и построенный диапазон, а если больше 30 минут то записываем просто дату и время, если конечно дальше не следует новый диапазон из дат. То есть из данного массива должно получиться примерно следующее:
Array
(
[0] => Array
(
[date] => 2020-02-07
[range] => 9:00
)
[1] => Array
(
[date] => 2020-02-07
[range] => 10:30 - 11:30
)
[2] => Array
(
[date] => 2020-02-08
[range] => 09:00 - 09:30
)
[3] => Array
(
[date] => 2020-02-08
[range] => 11:00
)
)
У меня пока мало идей и получается совсем что-то непотребное! Вот код:
//Инициализируем массив на выходе
$order = array();
//Инициализируем счетчик для ключей в массиве на выходе
$counter = 0;
//$data - это тот самый массив в начале вопроса
foreach($data as $date => $timesArr) {
$order[$counter]['date'] = $date;
$timesArrKeys = array_keys($timesArr);
$start = current($timesArrKeys);
if(count($timesArrKeys) > 1) {
$range = array();
while ($time = current($timesArrKeys)) {
$next = next($timesArrKeys);
if((strtotime($next) - strtotime($time)) == 1800) {
$range = $start . ' - ' . $next;
$order[$counter]['range'] = $range;
}else{
$start = $next;
$counter++;
$order[$counter]['date'] = $date;
$order[$counter]['range'] = $start;
}
next($timesArrKeys);
}
}else{
$order[$counter]['date'] = $date;
$order[$counter]['range'] = current($timesArrKeys);
}
}
По сути, этот код умеет формировать диапазоны, но он пропускает первые элементы, у которых отсутствует следующий элемент.
Чутка упростил входные значения чтоб не загромождать код. Суть такая: в цикле оперируем тремя курсорами - на текущее значение в итерации и на два прошлых - с которого начинался диапазон и ровно предыдущее значение в цикле. Теперь принимаем очень простое решение и либо бежим дальше, либо накапливаем результат.
function extractRangeList(array $in): array
{
$out = [];
$from = null;
$prev = null;
foreach ($in as $time) {
// начало нового диапазона
if (null === $from) {
$from = $time;
$prev = $time;
continue;
}
$inc = strtotime($time) - strtotime($prev);
// продолжение диапазона найденного ранее
if ($inc === 1800) {
$prev = $time;
continue;
}
// конец диапазона
$out[] = $from === $prev
? "{$from}"
: "{$from} - {$prev}";
$from = $time;
$prev = $time;
}
// вышли не закрыв диапазон?
if ($from !== null) {
$out[] = $from === $prev
? "{$from}"
: "{$from} - {$prev}";
}
return $out;
}
$in = [
'A' => ['09:00' => 0, '10:30' => 0, '11:00' => 0, '11:30' => 0],
'B' => ['09:00' => 0, '09:30' => 0, '11:00' => 0],
];
$expected = [
['date' => 'A', 'range' => '09:00'],
['date' => 'A', 'range' => '10:30 - 11:30'],
['date' => 'B', 'range' => '09:00 - 09:30'],
['date' => 'B', 'range' => '11:00'],
];
$out = [];
foreach ($in as $date => $prices) {
$rangeList = extractRangeList(array_keys($prices));
foreach ($rangeList as $range) {
$out[] = ['date' => $date, 'range' => $range];
}
}
assert($expected === $out);
В общем, решил эту задачу. На данный момент, пока очень криво и в несколько циклов. Но хотя бы понят алгоритм. Осталось только код сделать более изящным, но это я уже сам! Вот код:
$order = array(); //инициализируем массив для конечного результата
$counter = 0; //Это ключ для отделения позиций в $order
foreach($data as $date => $timesArr) {
$timesArrKeys = array_keys($timesArr); //Со значениями работать проще чем с ключами
//Если только 1 элемент в массиве то инкрементируем $counter на всякий случай и добавляем его в общий массив
if(count($timesArrKeys) > 1) {
$begin = 0; //Индикатор того начался ли диапазон
$counter++;
foreach($timesArrKeys as $k => $v) {
//Объявляем сразу следующий элемент в итерации для проверки
//Обращаю внимание, что для того, чтобы эта переменная не была статичной в каждой итерации, нужно руками смещать указатель через next()
$next = current($timesArrKeys);
if($begin) {
$order[$counter]['date'] = $date;
$order[$counter]['range'] .= $v . ',';
if((strtotime($next) - strtotime($v)) == 1800) {
next($timesArrKeys);
continue;
}else{
$begin = 0;
next($timesArrKeys);
continue;
}
}
if((strtotime($next) - strtotime($v)) == 1800) {
$counter++;
$begin = 1;
$order[$counter]['date'] = $date;
$order[$counter]['range'] .= $v . ',';
}else{
$counter++;
$begin = 0;
$order[$counter]['date'] = $date;
$order[$counter]['range'] = $v;
}
next($timesArrKeys);
}
}else{
$counter++;
$order[$counter]['date'] = $date;
$order[$counter]['range'] = current($timesArrKeys);
}
}
$order = array_values($order);
foreach($order as $k => &$v) {
if(!empty($v['range']) && (strpos($v['range'], ',') || strpos($v['range'], ',') === 0)) {
$v['range'] = rtrim($v['range'], ',');
$array = explode(',', $v['range']);
if(count($array) >= 2) {
$v['range'] = array_shift($array) . ' - ' . array_pop($array);
}
}
}
return $order;
Т.е. смысл здесь следующий: В начале цикла с датами $timesArrKeys
проверяем на предмет $begin
т.е. не начался ли период в цикле когда текущий и следующий элемент с разницей в 30 минут. Изначально $begin - 0
поэтому при первой итерации этот участок пропускается и переходит сразу к следующему участку - if((strtotime($next) - strtotime($v)) == 1800)
где мы сверяем текущий элемент и следующий в цикле и если тут true
то выставляем $begin = 1
, что значит, что при следующей итерации сработает первое условие в котором уже будет проводиться проверка на наличие следующего элемента, удовлетворяющего диапазону и если удовлетворяет, то мы просто смещаем указатель для нашего $next
и пропускаем итерацию, а на следующей мы опять попадем под $begin
и добавим через запятую элемент, т.к. в прошлой итерации мы выяснили что он подходит, но вот если уже следующий элемент не подходит то сработает else
где мы выставим $begin = 0
и следующий элемент подпадет под корневой else
в этом foreach
.
$counter
получится сбитый, но он нам не принципиален и его можно обнулить через array_values
как я и сделал для второго цикла... Не знаю даже понятно это или нет!
п.с. Если интересно, необходимо это для модуля бронирования, где можно выбрать дату с шагом в пол часа и надо чтобы подряд часы группировались в диапазон и формировались в товар, а если идет разрыв во времени - это был отдельный товар, но с той же датой.
Имеем исходный массив:
$array = [
'2020-02-07' => ['09:00' => 1500, '10:30' => 1500, '11:00' => 1500, '11:30' => 1500],
'2020-02-08' => ['09:00' => 1500, '09:30' => 1500, '11:00' => 1500],
];
Пришло в голову сделать следующим образом:
$result = [];
foreach ($array as $k => $v)
{
$v = array_map(function($a) {
return strtotime($a);
}, array_keys($v));
for ($i = 0; $i < count($v); $i++) {
$a = $v[$i];
$b = $a;
while (isset($v[$i + 1]) && $v[$i + 1] - $v[$i] === 1800) {
$b = $v[$i + 1];
$i++;
}
$range = $a == $b
? date('H:i', $a)
: date('H:i', $a) .' - '. date('H:i', $b);
$result[] = ['date' => $k, 'range' => $range];
}
}
print_r($result);
https://3v4l.org/oHW1A
А для любителей кода-гольфа, я оставлю следующее (работает только на php7.4+):
$result = [];
$f = fn($p) => date('H:i', $p);
foreach ($array as $k => $v)
{
$v = array_map(fn($a) => strtotime($a), array_keys($v));
for ($i = 0; $i < count($v); $i++) {
[$a, $b] = [$v[$i], &$a];
while (isset($v[$i + 1]) && $v[$i + 1] - $v[$i] === 1800) [$b = $v[$i + 1], $i++];
$result[] = ['date' => $k, 'range' => $a == $b ? $f($a) : $f($a).' - '.$f($b)];
}
}
print_r($result);
https://3v4l.org/qjNjT
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Имеется такой код получения страницыПри попытке получить первый url, срабатывает редирект и переменные $error и $response_string пустые
Цель: Сделать карту на весь экран без дублирования по горизонтали и верхнего "пустого пространства"Проблема: Вместо дублирования карты появились...