#php #arrays #sorting
Вопрос:
Поэтому я строю массив различных дат. Дни рождения, юбилеи и праздники. Я хотел бы упорядочить массив, по которому происходит следующее, по сути, отсортировать октябрь по сентябрь (перенос на следующий год).
так что, если мой массив
$a = ([0]=>"1980-04-14", [1]=>"2007-06-08",
[2]=>"2008-12-25", [3]=>"1978-11-03")
Я бы хотел отсортировать его так, чтобы он был упорядочен
$a = ([0]=>"1978-11-03", [1]=>"2008-12-25",
[2]=>"1980-04-14", [3]=>"2007-06-08")
потому что ноябрьское «событие» — это то, которое произойдет в следующий раз (исходя из того, что сейчас октябрь).
Я пытаюсь понять, где находится моя функция cmp
function cmp($a, $b)
{
$a_tmp = split("-", $a);
$b_tmp = split("-", $b);
return strcmp($a_tmp[1], $b_tmp[1]);
}
Я не уверен, как это изменить, чтобы получить желаемый эффект.
Комментарии:
1. Мне нравится это свидание. Явно опечатка. теперь исправлено.
Ответ №1:
function relative_year_day($date) {
$value = date('z', strtotime($date)) - date('z');
if ($value < 0)
$value = 365;
return $value;
}
function cmp($a, $b)
{
$aValue = relative_year_day($a);
$bValue = relative_year_day($b);
if ($aValue == $bValue)
return 0;
return ($aValue < $bValue) ? -1 : 1;
}
$a = array("1980-04-14", "2007-06-08",
"2008-12-25", "1978-11-03");
usort($a, "cmp");
Ответ №2:
У меня возникло бы искушение установить первоначальный год события, а затем добавить к нему достаточно целых лет, чтобы убедиться, что значение больше, чем ваша контрольная дата (обычно сегодняшняя дата). Или, возможно, больше или равно контрольной дате. Затем вы можете отсортировать данные в простом порядке по дате.
Отредактировано, чтобы добавить:
Я недостаточно хорошо владею PHP, чтобы дать ответ на этот вопрос, но вот решение на Perl.
#!/bin/perl -w
# Sort sequence of dates by next occurrence of anniversary.
# Today's "birthdays" count as low (will appear first in sequence)
use strict;
my $refdate = "2008-10-05";
my @list = (
"1980-04-14", "2007-06-08",
"2008-12-25", "1978-11-03",
"2008-10-04", "2008-10-05",
"2008-10-06", "2008-02-29"
);
sub date_on_or_after
{
my($actdate, $refdate) = @_;
my($answer) = $actdate;
if ($actdate lt $refdate) # String compare OK with ISO8601 format
{
my($act_yy, $act_mm, $act_dd) = split /-/, $actdate;
my($ref_yy, $ref_mm, $ref_dd) = split /-/, $refdate;
$ref_yy if ($act_mm < $ref_mm || ($act_mm == $ref_mm amp;amp; $act_dd < $ref_dd));
$answer = "$ref_yy-$act_mm-$act_dd";
}
return $answer;
}
sub anniversary_compare
{
my $r1 = date_on_or_after($a, $refdate);
my $r2 = date_on_or_after($b, $refdate);
return $r1 cmp $r2;
}
my @result = sort anniversary_compare @list;
print "Before:n";
print "* $_n" foreach (@list);
print "Reference date: $refdaten";
print "After:n";
print "* $_n" foreach (@result);
Очевидно, что это не очень эффективно — чтобы сделать его эффективным, вы должны один раз вычислить значение date_on_or_after (), а затем отсортировать эти значения. Сравнение Perl несколько своеобразно — переменные $a и $b являются волшебными и появляются как бы из ниоткуда.
При запуске скрипт производит:
Before:
* 1980-04-14
* 2007-06-08
* 2008-12-25
* 1978-11-03
* 2008-10-04
* 2008-10-05
* 2008-10-06
* 2008-02-29
Reference date: 2008-10-05
After:
* 2008-10-05
* 2008-10-06
* 1978-11-03
* 2008-12-25
* 2008-02-29
* 1980-04-14
* 2007-06-08
* 2008-10-04
Обратите внимание, что это в значительной степени уводит вопрос о том, что происходит с 29 февраля, потому что это «работает» для этого. В принципе, он сгенерирует «дату» 2009-02-29, которая будет правильно сравниваться в последовательности. Годовщина 2000-02-28 будет указана до годовщины 2008-02-29 (если в данные были включены 2000-02-28).
Комментарии:
1. Я подозреваю, что мой тоже не очень эффективен. Но я также не чувствую, что у меня ошибка 02-29.
Ответ №3:
используйте strtotime() для преобразования всех дат в метку времени, прежде чем добавлять их в массив, затем вы можете отсортировать массив в возрастающем (также хронологическом) порядке. Теперь все, что вам нужно сделать, это разобраться с датами в прошлом, что легко сделать, сравнив их с текущей меткой времени
т.е.
for ($i=0; $i<count($a); $i ){
if ($currentTimestamp > $a[$i]){
unset($a[$i]);
}
}
Ответ №4:
Нет причин изобретать велосипед заново. Если вас не волнуют ключи, вы можете воспользоваться этим.
$a = array_combine(array_map('strtotime', $a), $a);
ksort($a);
Или если вы хотите определить свой собственный обратный вызов.
function dateCmp($date1, $date2) {
return (strtotime($date1) > strtotime($date2))?1:-1;
}
usort($a, 'dateCmp');
Если вы хотите, чтобы ключи были правильно связаны, просто позвоните вместо этого в uasort.
uasort($a, 'dateCmp');
Я быстро проверил скорость, и функции обратного вызова стали на порядок медленнее.
Ответ №5:
Поэтому мне пришло в голову просто добавить 12 к любому месяцу, который меньше моего целевого месяца. Что сейчас и работает.
таким образом, конечная функция
function cmp($a, $b)
{
$a_tmp = explode('-', $a['date']);
$b_tmp = explode('-', $b['date']);
if ($a_tmp[1] < date('m')) {
$a_tmp[1] = 12;
}
if ($b_tmp[1] < date('m')) {
$b_tmp[1] = 12;
}
return strcmp($a_tmp[1] . $a_tmp[2], $b_tmp[1] . $b_tmp[2]);
}
Комментарии:
1. Это не сработает, если вы попытаетесь включить день в уже прошедший месяц (например, если вы включите в свой список 10/1/2000).
2. Что произойдет, если одна из дат будет 2008-02-29?
3. Учитывая, что сегодня 2008-10-05, как ваш код показывает обработку записей 2008-10-04, 2008-10-05, 2008-10-06? В частности, обратите внимание, что следующее празднование 2008-10-04 года состоится позже, чем все остальные рассматриваемые даты. Вы не определили, считается ли сегодняшняя дата в этом или следующем году.
4. И, черт возьми, » шоу » в моем предыдущем комментарии излишне.
5. Существует вероятность отображения 10-04. На данный момент меня это не беспокоит. Возможно, мне придется добавить 30 к любому дню сверх числа текущего. На данный момент это просто сортировка строк, поэтому такие дни, как 02-29 и 10-45, не имеют значения, они просто сортируются по порядку.
Ответ №6:
Не сравнивайте строки, вместо этого используйте секунды с 1970 года (ints):
$date1 = split("-", $a);
$date2 = split("-", $b);
$seconds1 = mktime(0,0,0,$date1[1],$date1[2],$date1[0]);
$seconds2 = mktime(0,0,0,$date2[1],$date2[2],$date2[0]);
// eliminate years
$seconds1 %= 31536000;
$seconds2 %= 31536000;
return $seconds1 - $seconds2;
Также я не знаю PHP, но думаю, что суть верна.
Правка: Функция сравнения инкапсулирована для выполнения сравнения, не более того. Чтобы упорядочить список в соответствии с исходным вопросом, отсортируйте массив с включенной сегодняшней датой, найдите сегодняшнюю дату в массиве, а затем переместите элементы перед этой позицией в конец в порядке возрастания по позиции.
Комментарии:
1. Ммм, да, вам придется умножить тикс1 и тикс2 по модулю на 31536000, чтобы исключить годы, но, боюсь, это будет работать правильно.