пятница, 10 апреля 2020 г.

Уникальные или неуникальные значения в массиве

В качестве разминки сегодня решим следующую задачу - предположим, есть массив, некоторые значения в нем повторяются. Требуется получить массив неуникальных (повторяющихся) значений или массив не повторяющихся (уникальных) значений.

Сразу к примеру:

$array = [1, 2, 3, 4, 3, 4, 5, 6, 6];

// Для любого из вариантов задачи нам потребуется знать
// сколько раз в массиве встречается каждое значение
$freqs = array_count_values($array);

// Массив уникальных значений - [1, 2, 5]
$unique = array_keys(
    array_filter(
        $freqs,
        function ($freq) { return 1 === $freq; }
    )
);

// Массив неуникальных значений - [3, 4, 6]
$nonunique = array_keys(
    array_filter(
        $freqs,
        function ($freq) { return 1 < $freq; }
    )
);

// Фильтрация исходного массива с оставлением только повторяющихся значений -[3, 4, 3, 4, 6, 6]
$allNonunique = array_filter(
    $array,
    function ($v) use ($freqs) { return 1 < $freqs[$v]; }
);


В общем-то всё, на досуге можете расширить этот код и задать количество появлений в виде переменной, а не фиксированного значения 1.

понедельник, 6 апреля 2020 г.

Неявное поведение DateTime::createFromFormat

Данный пост есть результат недавнего обсуждения в одном из php-каналов некоторого странного (как кажется изначально) поведения функции DateTime::createFromFormat.

Рассмотрим простейший код, надо отметить, что исследуемое поведение отмечается только 31-го числа каждого месяца (ну и плюс 29-30 для февраля):

$months = [1, 2, 3, 4];

foreach ($months as $month) {
    $dt = '2019-' . $month;
    echo $dt . ': ' . (\DateTime::createFromFormat('Y-m', $dt))->format('Y-m-d') . PHP_EOL;
}


Обратите внимание, что при создании объекта не указывается день. Так как день не указан, то разумно предположить (это же подтверждается в комментариях), что php берет в качестве дня текущий день запуска скрипта из системных настроек.

Таким образом, запуская скрипт, например, 31-го мая, получим такой вывод:

// ожидаемо, в январе есть 31 число
2019-1: 31.01.2019
// в феврале-2019 нет 29 (и 30 и 31) числа, потому 31-му февраля
// соответствует третье марта (а 29-му февраля - первое марта)
2019-2: 03.03.2019
// ожидаемо, в марте есть 31 число
2019-3: 31.03.2019
// в апреле нет 31 числа, и следующим после 30 апреля идет 1 мая
2019-4: 01.05.2019


Как видим, происходит не то, что ожидается, хотя в другие дни - всё работает нормально, и даже имеющиеся тесты будут проходить.

Можно, конечно, рассуждать о том, что раз день не указан, то может надо кидать эксепшен при создании объекта, но так как такого поведения нет, то придерживаемся мудрого принципа "Явное лучше, чем неявное" и повнимательней пишем свой код.

Всем здоровья)

воскресенье, 8 марта 2020 г.

Строковые представления типов данных, часть 3

В предыдущих частях (часть 1, часть 2) мы рассмотрели как к строковому типу приводятся скалярные типы данных и null. Сейчас перейдем к таким типам данных как массив, объект и ресурс.

Массив, сразу смотрим пример:

$var = [1,2,3];
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
Notice: Array to string conversion in file on line ..
Notice: Array to string conversion in file on line ..
Notice: Array to string conversion in file on line ..
Array
Эту строку я конкатенирую с переменной Array
В эту строку я подставляю переменную Array


Итак, строковое представление массива это просто слово Array. И так как это не ожидаемая операция над массивом, то выводится еще и Notice. Таким образом, просто взять и вывести массив на экран не получится. Можно обойти массив с помощью цикла и вывести элементы в нужном формате. Естественно, если элементы тоже массивы - их также требуется обойти циклом. И так далее. В случае если массив одномерный - можно воспользоваться функцией implode. Также, если у вас при вставке в БД в поле появляется слово Array - вы поняли, в чем ошибка.

Объект. С объектами ситуация следующая: не всякий объект можно вывести на экран. Например, возьмем такой класс и попытаемся вывести на экран объект данного класса:

class myStdClass
{
    public $intField;
    public $strField;
}

$var = new myStdClass();
$var->intField = 42;
$var->strField = 'Forty two';
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
Recoverable fatal error: Object of class stdClass could not be converted to string in ... on line ...


Получаем фатальную ошибку, так как php не представляет как привести данный объект к строковому представлению. Но ситуация поправима. Для приведения к объекта к строке требуется определить в классе "магический" метод __toString. Метод, естественно, должен вернуть некую строку, которая и будет строковым представлением объекта.
Модифицируем класс из предыдущего примера:

class myStdClass
{
    public $intField;
    public $strField;

    public function __toString(): string
    {
        return 'intField: ' . $this->intField . '; strField: ' . $this->strField;
    }
}

$var = new myStdClass();
$var->intField = 42;
$var->strField = 'Forty two';
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
intField: 42; strField: Forty two
Эту строку я конкатенирую с переменной intField: 42; strField: Forty two
В эту строку я подставляю переменную intField: 42; strField: Forty two


Как видим, теперь наш объект прекрасно приводится к строковому представлению.

Я не знаю ни одного случая, когда требуется строковое представление ресурса. Но для полноты картины посмотрим на следующий код:

$var = fopen('/tmp/1.tmp', 'a');
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
Resource id #5
Эту строку я конкатенирую с переменной Resource id #5
В эту строку я подставляю переменную Resource id #5


Как видим, толку в этом выводе мало, только убедиться что в переменной хранится ресурс. Надеяться на то, что ИД ресурса (в данном выводе - 5) не изменится при следующем запуске, также не стоит.

На этом я завершаю цикл статей по преобразованию типов данных в строки, всем спасибо за внимание.

воскресенье, 23 февраля 2020 г.

Строковые представления типов данных, часть 2

Продолжим рассматривать php преобразует различные типы данных к строковому виду. Первая часть здесь.

Рассмотрим тип данных int. Переменная типа int может быть задана в четырех системах счисления: десятичной, восьмеричной, шестнадцатеричной и двоичной. Независимо от системы счисления, вывод будет преобразован к десятичной системе счисления:

$a = 0;
$b = 42;
$c = 0b101011;   // 43 в десятичной
$d = 054;        // 44 в десятичной
$e = 0x2D;       // 45 в десятичной
$f = -42;

echo $a . PHP_EOL;
echo $b . PHP_EOL;
echo $c . PHP_EOL;
echo $d . PHP_EOL;
echo $e . PHP_EOL;
echo $f . PHP_EOL;
// вывод:
0
42
43
44
45
-42


Числа, превышающие размер типа int (PHP_INT_MAX / PHP_INT_MIN), автоматически конвертируются в тип float и выводятся соответствующе:

$a = PHP_INT_MAX;
$b = 2 + PHP_INT_MAX;
$c = PHP_INT_MIN;
$d = PHP_INT_MIN -1;

echo $a . PHP_EOL;
echo $b . PHP_EOL;
echo $c . PHP_EOL;
echo $d . PHP_EOL;
// вывод:
9223372036854775807
9.2233720368548E+18
-9223372036854775808
-9.2233720368548E+18


Для того чтобы вывести int переменную не в десятичной, а другой системе счисления, нужно воспользоваться функциями форматирования, например, sprintf/printf:

$a = 42;

echo sprintf("%o", $a) . PHP_EOL;  // восьмеричная
echo sprintf("%x", $a) . PHP_EOL;  // шестнадцатиричная, буквы в нижнем регистре
echo sprintf("%X", $a) . PHP_EOL;  // шестнадцатиричная, буквы в верхнем регистре
echo sprintf("%b", $a) . PHP_EOL;  // двоичная
// вывод:
52
2a
2A
101010


Переходим к выводу переменных типа float.

$floatNums = [
    // Первый способ записи float-чисел
    4.2,
    4.2222,
    4.22222222,
    0.2,
    0.0002,
    0.00002,
    0.000002,
    // Второй способ записи float-чисел
    1.2e4,
    // Третий способ записи float-чисел
    1.2E4,
    5E-3,
    5E-4,
    5E-5,
    // int значения превышающие размер типа int конвертируются во float
    PHP_INT_MAX + 20,
];

foreach ($floatNums as $num) {
    echo $num . PHP_EOL;
}
// вывод:
4.2
4.2222
4.22222222
0.2
0.0002
2.0E-5    //числа с пятью и более знаками после запятой выводятся в нотации с основанием и мантиссой
2.0E-6
12000
12000
0.005
0.0005
5.0E-5
9.2233720368548E+18


Как видим - числа выводятся с тем же количеством знаков после запятой, что были указаны при их объявлении. Для указания нужного числа знаков после запятой - пользуйтесь функциями форматирования, например, теми же sprintf/printf или number_format:

$floatNum = 4.23456789;
echo sprintf('%f', $floatNum) . PHP_EOL;
echo sprintf('%.2f', $floatNum) . PHP_EOL;
echo sprintf('%.5f', $floatNum) . PHP_EOL;
echo sprintf('%.12f', $floatNum) . PHP_EOL;

echo number_format($floatNum) . PHP_EOL;
echo number_format($floatNum, 2) . PHP_EOL;
echo number_format($floatNum, 5) . PHP_EOL;
echo number_format($floatNum, 6) . PHP_EOL;

// вывод
4.234568   // по умолчанию выводится 6 знаков после запятой
4.23
4.23457    // можно подумать что это округление, но нет, такой вывод связан с представлением числа в памяти программы
4.234567890000     // оставшиеся позиции заменяются нулями
4
4.23
4.23457    // аналогично, это НЕ округление
4.234568


Оставшиеся типы данных - массив, объект и ресурс, рассмотрим в заключительном посте.

вторник, 17 декабря 2019 г.

Сборка мусора в php

Недавно встретился хороший пост для начинающих о сборке мусора (он же garbage collection) в php. Далее я попытаюсь перевести этот пост на русский и добавить немного собственных данных. Ссылка на оригинальный пост - в конце.

Начнем с того, что так как php - язык интерпретируемый, то вам не нужно заморачиваться управлением памятью - выделением памяти, и что более важно - очисткой памяти. Этим в php занимается специальный механизм, называемый сборкой мусора (или garbage collection, или же gc).

Сборка мусора работает тремя способами:
  1. При уходе переменной из области видимости
  2. При подсчете ссылок
  3. При сборе циклических ссылок

четверг, 12 декабря 2019 г.

Or-pattern в glob

Короткий пост о том, как в аргументе функции glob использовать шаблон или.

Предположим, каталог содержит следующие файлы:

- file.txt
- picture
- picture.gif
- picture.jpg
- picture.png
- picture1.jpg


И требуется получить файлы имеющие в названии только "picture" ("picture1" не подходит) и с расширениями jpg, png или вообще без расширения.

Естественно, простейшим решением можно считать объединение результатов трех вызовов glob:

print_r(array_merge(
    glob('./picture\.jpg'),
    glob('./picture\.png'),
    glob('./picture')
));


Но давайте попробуем ограничиться одним вызовом. В этом нам поможет изучение странички мануала функции glob, а точнее - списка параметров. В нем нас интересует второй аргумент flags и его значение GLOB_BRACE:

print_r(
    glob('./picture{,\.jpg,\.png}', GLOB_BRACE)
);


Получаем тот же самый набор файлов, что и в первом примере (сортировка не в счет). На этом всё, читайте почаще мануалы и находите решения попроще. Кстати, в комментах можете поделиться еще более хитрыми шаблонами для glob, если у вас таковые есть.

среда, 20 ноября 2019 г.

Удаление всех таблиц в схеме данных postgresql

Иногда требуется удалить все таблицы в выбранной схеме в базе данных postgresql.

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

SELECT 'drop table if exists "' || tablename || '" cascade;' as pg_tbl_drop
FROM pg_tables
WHERE schemaname='public';


Естественно, вместо schemaname='public' нужно подставить вашу конкретную схему.

Выполнив все полученные запросы, получаем схему без таблиц. Однако, может возникнуть ситуация, что в схеме остались последовательности (sequences). Получить все запросы на удаление последовательностей можно таким запросом:

SELECT 'drop sequence if exists "' || relname || '" cascade;' as pg_sec_drop
FROM pg_class
WHERE relkind = 'S';


Посмотреть все последовательности (на всякий случай) можно с помощью запроса:

SELECT c.relname
FROM pg_class c
WHERE c.relkind = 'S';


И как всегда - при удалении важных данных не забудьте про бэкап.