Отправляет email-рассылки с помощью сервиса Sendsay

[TC] PHP: Как убрать пустые элементы массива?

Vande omentaina, Tiflocomp!
Есть такая задача.
Есть чат, в котором сообщения имеют дату (тип поля Datetime).
Некоторым пользователям надо видеть лог этого чата. Поскольку лог
огромный, я решил выдать им список дней, после выбора дня уже показать
лог по этому дню.
Я пока решаю задачу так: выбираю всю таблицу, загоняю её в массив и
получаю дни. Вот кусок кода:
echo "<form action=\"showlog.php\" method=post>

<select name=uday size=1>";
$q=mysql_query("SELECT `Date` FROM `Chat`", $link);
for ($i=0; $i<mysql_num_rows($q); $i++) {
$f=mysql_fetch_array($q);
// Отрезаем время
$DateAndTime=explode (" ", $f[Date]);
$date=$DateAndTime[0];
// Накапливаем массив дней
$d[$i]=$date;
}
// Поскольку записей много, надо получить уникальные дни
$d=array_unique ($d);
// Внимание! что надо написать вместо этой функции, чтобы в списке
// выводились только существующие элементы?
array_values ($d);
// Ну а дальше просто обработка, чтобы выводилось "10 мая 2009 года",
// а не "2009-05-10
for ($y=0; $y<count($d); $y++) {
$DateWithoutTime=explode ("-", $d[$y]);
$year=$DateWithoutTime[0];
$month=$DateWithoutTime[1];
$day=$DateWithoutTime[2];
switch ($month) {
case "01": $month="января"; break;
case "02": $month="февраля"; break;
case "03": $month="марта"; break;
case "04": $month="апреля"; break;
case "05": $month="мая"; break;
case "06": $month="июня"; break;
case "07": $month="июля"; break;
case "08": $month="августа"; break;
case "09": $month="сентября"; break;
case "10": $month="октября"; break;
case "11": $month="ноября"; break;
case "12": $month="декабря"; break;
}
echo "<option value=\"$d[$y]\"> $day $month $year</option>";
}
echo "</select>";

Итак, вопросы:
1. Можно ли всё это сделать менее громоздко: например, как-нибудь с
помощью DISTINCT выбрать уникальные даты; вообще выбирать не все
записи...?
2. Как убрать пустые элементы (пересортировать массив)? что я имею в
виду: в чате, скажем, 50000 сообщений. В такой-то день их было 700. И
вот даже после array_unique() остаются 700 пустых элементов, которые
выводятся в списке. Или не выводятся, но список обрывается где-то на
втором дне (потому что так странно работает count). Как это обойти?
Извините за ламерство, но что-то не догоняю...
Спасибо!

Ответить   Anarendil Sun, 10 May 2009 14:43:29 +0300 (#858169)

 

Ответы:

Доброго времени суток, уважаемая рассылка и Anarendil.

Sunday, May 10, 2009, 2:43:29 PM, you wrote:

function getMonth($n)
{
$month = array("января", "февраля", "марта", "апреля", "мая", "июня", "июля",
"августа", "сентября", "октября", "декабря");
$n = ($n - 1) % 12;
return $month[$n];
}

while ($row = mysql_fetch_array($q))
{

$DateAndTime=explode (" ", $row['Date']);

// элементы с одинаковыми ключами (датами) будут просто перекрываться
$d[$date] = preg_replace("/([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})/e", "'\\3 '.getMonth('\\2').'
\\1'", $date);
', $date);

foreach ($d as $date => $str)
{
echo "<option value=\"$date\"> $str</option>";

см. вариант выше. Строки без "A>" были мною изменены.

Пожалуйста!
п.с. если будет бить какие-то ошибки, пишите (писал на память и без
тестирования).

Ответить   Олег Sun, 10 May 2009 18:11:36 +0300 (#858249)

 

Vande omentaina, Олег!

Хорошо, да не очень красиво, я думаю.
А неужели нельзя вот так взять и убрать пустые элементы какой-нибудь
мегафункцией?

Ответить   Anarendil Sun, 10 May 2009 21:27:22 +0300 (#858262)

 

Доброго времени суток, уважаемая рассылка и Anarendil.

Sunday, May 10, 2009, 9:27:22 PM, you wrote:

Накапливать данные в огромный массив, а потом их от туда удалять, тоже.

А от куда у вас взялись пустые элементы? Ни одна из
ваших функций их не создает.
array_unique - оставляет в выходном массиве только каждый первый
уникальный элемент
array_values - переиндексирует выходной массив

И любая мегафункция пишется ручками. :-)

Ответить   Олег Sun, 10 May 2009 22:20:48 +0300 (#858293)

 

Vande omentaina, Олег!

А вот почему-то не работает оно так. У меня всё равно 50000 элементов,
просто некоторые из них не имеют значений: print_r() даёт примерно
такое:
[26552]=>

И всё. Что с этим делать?

Ответить   Anarendil Sun, 10 May 2009 22:51:10 +0300 (#858302)

 

Здравствуйте, Anarendil <anarend***@a*****.net>.

В своём сообщении в рассылку Tiflocomp с темой "[TC] Re[4]: PHP: Как
убрать пустые элементы массива?" от 10.05.2009 в 23:51 Вы писали:

Ответить   Comoderator Mon, 11 May 2009 01:23:03 +0400 (#858368)

 

Доброго времени суток, уважаемая рассылка и Anarendil.

Sunday, May 10, 2009, 2:43:29 PM, you wrote:

Вот от куда у вас пустые элементы!
функция сработала, а куда ей вернуть результат?
$d = array_values($d);

Пожалуйста!

Ответить   Олег Sun, 10 May 2009 22:59:43 +0300 (#858304)

 

Доброго времени суток, уважаемая рассылка и Anarendil.

Sunday, May 10, 2009, 2:43:29 PM, you wrote:

Могу предложить еще один вариант:

function getMonth($n)
{
$month = array("января", "февраля", "марта", "апреля", "мая", "июня", "июля",
"августа", "сентября", "октября", "декабря");
$n = ($n - 1) % 12;
return $month[$n];
}

$q=mysql_query("SELECT `Date` FROM `Chat`", $link);
while ($row = mysql_fetch_array($q))
{
// Отрезаем время
$DateAndTime=explode (" ", $row['Date']);
$date=$DateAndTime[0];
// Накапливаем массив дней
// элементы с одинаковыми ключами (датами) будут просто перекрываться
$d[$date] = "";
}

$d = array_keys($d);
echo "<form action=\"showlog.php\" method=post>
<p>Выберите день:<br>
<select name=uday size=1>";
foreach ($d as $date)
{
$str = preg_replace("/([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})/e", "'\\3 '.getMonth('\\2').'
\\1'", $date);
echo "<option value=\"$date\">$str</option>";
}
echo "</select>";

В этом варианте нет больших массивов с повторяющимися данными и функция preg_replace
срабатывает только столько раз, сколько нужно!

Ответить   Олег Mon, 11 May 2009 05:45:54 +0300 (#858421)

 

Приветствую всех.
Олег пишет:

Регулярное выражение можно записать короче:
"/(\d{4})-(\d{2})-(\d{2})/e"

Однако в вашем коде есть подводные грабли: Что произойдет, если дата не будет
удовлетворять указанному регулярному выражению? А произойдет то, что замена не
будет выполнена и переменная $str получит то же самое значение, что и $date.
В данном случае такая ситуация не обрабатывается скриптом, как ошибка, да и последствия
от нее не фатальные (хотя это было бы не лишним на стадии отладки и при эксплуатации
могло бы служить косвенным признаком целостности данных в базе), но не все скрипты
так безобидны...

Успехов. Анатолий.

Ответить   "i_chay" Mon, 11 May 2009 10:14:09 +0500 (#858461)

 

Vande omentaina, i_chay!

Спасибо!
Скажите, а что такое ключ E в конце регекспа?

Ответить   Anarendil Mon, 11 May 2009 15:15:17 +0300 (#858592)

 

Приветствую всех.
Андрей пишет:

1. Логичнее было бы поместить вызов mysql_query до начала вывода формы, а потом
по результату mysql_query решать, что и в каком виде показывать пользователю.
2. Время можно отсечь сразу в запросе, указав нужный формат представления даты,
и это позволит использовать DISTINCT в запросе:
SELECT DISTINCT DATE_FORMAT(date,'%d.%m.%y') AS date FROM...

3. Формат можно подобрать такой, чтобы не пришлось затем применять собственные
велосипеды для преобразования номера месяца в его название. Единственное, что
тут может помешать, это не установленная или неправильно установленная локаль,
то есть вы будете получать английские названия месяцев.
На этом можно было бы и завершить обсуждение, однако, на мой взгляд, следует
пройтись и по другим ошибкам:

Вместо mysql_fetch_array, если нет специфических причин, лучше использовать
mysql_fetch_object или mysql_fetch_assoc (как минимум, при обработке результатов
этих функций не играет роли порядок перечисления полей в запросе).

Здесь удобнее воспользоваться хэш-таблицей, то есть массивом со строковыми ключами:
$day[$date] =1;
или
isset($day[$date]) or $day[$date] =1;
Можно, при желании, накапливать результат, т.е. подсчитывать количество записей,
приходящихся на один день.
В результате у вас сразу будет массив с уникальными ключами-строками в виде нужной
даты (речь идет уже о подходе вообще, так как в конкретном случае с вашим чатом
все можно сделать через sql-запрос).

По поводу этой ошибки уже сказал Олег, но можно обойтись и без вызова array_values
-- см. ниже.

В контексте вашего кода это неправильно. Надо
использовать
foreach($y as $day)
{
...
Если вы используете хэш-таблицу, то
foreach($d as $day=>$value)
{
...
Для foreach вызов array_values не требуется.

Дело в том, что array_unique создает так называемый разряженный массив, т.е.
часть позиций в нем пустые и вы пытаетесь обратиться к этим позициям по числовому
индексу (вообще-то, php должен обрадовать вас соответствующим notice, так что,
если вывод notice у вас отключен, лучше включите эти сообщения -- узнаете о
своем коде много нового:-)).

Во-первых, count() не обрывается, а возвращает реальное число элементов массива,
которое будет заведомо меньше, чем самый большой числовой индекс в вашем массиве.
Например, у вас 50тыс. записей, которые вы поместили в массив. Следовательно
самый большой индекс будет у элемента 49999.
После избавления от повторяющихся значений, вы получили, например, массив из
30 элементов (count() возвращает 30), но у последнего элемента в этом массиве
числовой индекс совсем не обязательно будет равен 29 -- он может оказаться и
102, и 37801, и 10223, etc.
Во-вторых, подсчитывать число элементов массива в каждой итерации цикла -- занятие
довольно-таки расточительное (возможно, php оптимизирует это место, но стоит
ли на это полагаться?).
В-третьих, то, что вы видите, происходит не на втором дне, а на элементах массива
с индексами от 1 до 699, а они у вас не существуют. А "второй день" в вашем
примере будет иметь числовой индекс 700 и пока вы до него дойдете, php завалит
страницу ругательствами по поводу отсутствующих элементов массива. И, как сказано
выше, если число существующих элементов массива меньше 700, то ваш вывод так
и не доберется до "второго дня".

Успехов. Анатолий.

Ответить   "i_chay" Mon, 11 May 2009 08:02:20 +0500 (#858422)

 

Vande omentaina, i_chay!

А где можно почитать про DATE_FORMAT? Как я понял, оно работает и с
DATE, и с DATETIME?

Похоже, на этом хосте так и есть(. SetLocale() как-то почему-то не
работает, приходится делать велосипеды.

А что это за зверь, можно поподробнее?

Это я конкретно ступил...(

о

Представляю, сколько нового узнает о своём коде второй
разработчик...))))) Поэтому notices на глобальном по крайней мере
уровне включать не буду).
Спасибо!)

Ответить   Anarendil Mon, 11 May 2009 15:28:24 +0300 (#858602)

 

Приветствую всех.
Андрей пишет:

http://ru.php.net/preg_replace

http://www.mysql.ru/docs/man/Date_and_time_functions.html
http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html

http://ru.php.net/mysql_fetch_object

Успехов. Анатолий.

Ответить   "i_chay" Tue, 12 May 2009 10:33:17 +0500 (#858787)