Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

PHP: масиви й рядки

20 питань · ~20 хв · Версія v3.0

Увійдіть, щоб продовжити

Функції масивів і їхні ключі, сортування, розпакування, рядки й багатобайтові функції, Unicode, JSON - питання всіх рівнів, від junior до lead.

За спробу
20
У пулі
73
Проходжень
0
Середній бал
-
Пройшли на 70%+
-

Питання для підготовки

15 питань

Усі три обходять масив і викликають функцію для кожного елемента, але повертають різне:

  • array_map - новий масив тієї ж довжини, кожен елемент перетворено.
  • array_filter - лише ті елементи, для яких функція повернула true. Ключі зберігаються.
  • array_reduce - одне значення, накопичене з усіх елементів.
$prices = [100, 250, 40];

array_map(fn (int $p): int => $p * 2, $prices);          // [200, 500, 80]
array_filter($prices, fn (int $p): bool => $p > 50);      // [0 => 100, 1 => 250]
array_reduce($prices, fn (int $sum, int $p): int => $sum + $p, 0); // 390

Пастки, про які питають:

  • Порядок аргументів різний: у array_map спершу функція, у array_filter - масив. Плутають постійно.
  • Після array_filter ключі «дірчасті» ([0, 2, 5]), і json_encode перетворить такий масив на об'єкт. Допомагає array_values().
  • array_filter без функції прибирає всі «порожні» значення - разом із 0 і '0', що інколи несподівано.

Жодна з функцій не змінює початковий масив - усі повертають новий.

Докладніше в документації: array_map

Рядок у PHP - це послідовність байтів, а не символів. strlen() рахує байти. У UTF-8 кожна кирилична літера займає 2 байти, тож «привіт» - 12 байтів.

strlen('привіт');        // 12
mb_strlen('привіт');     // 6
mb_substr('привіт', 0, 3); // 'при'
substr('привіт', 0, 3);  // зламаний символ: 'п' і половина 'р'

Правило: для тексту, де можуть бути не лише латинські літери, використовуйте mb_*-функції: mb_strlen, mb_substr, mb_strtoupper, mb_str_split. Байтові функції зріжуть символ посередині, і в результаті з'явиться «�».

Нюанси:

  • mb_* рахують кодові точки Unicode. Емодзі з модифікатором шкіри чи прапор складаються з кількох точок, і для «видимих символів» потрібен grapheme_strlen() з розширення intl.
  • strlen() не помилка - він потрібен, коли важливі саме байти: розмір тіла запиту, обмеження колонки в байтах.
  • У Laravel Str::length(), Str::limit() уже працюють через mb_*.

Докладніше в документації: mb_strlen

Обидва об'єднують масиви, але по-різному поводяться з однаковими ключами:

  • $a + $b - бере все з $a, а з $b додає лише ключі, яких у $a немає. Перемагає лівий.
  • array_merge($a, $b) - для рядкових ключів перемагає правий, а числові ключі перенумеровує з нуля й дописує в кінець.
$defaults = ['color' => 'red', 'size' => 'M'];
$options  = ['color' => 'blue'];

$options + $defaults;            // ['color' => 'blue', 'size' => 'M']
array_merge($defaults, $options); // ['color' => 'blue', 'size' => 'M']

[0 => 'a', 1 => 'b'] + [0 => 'c', 1 => 'd', 2 => 'e']; // ['a', 'b', 'e']
array_merge(['a', 'b'], ['c', 'd', 'e']);              // ['a', 'b', 'c', 'd', 'e']

Коли що:

  • + - накласти значення за замовчуванням: «мої опції, а чого бракує - з дефолтів».
  • array_merge або spread [...$a, ...$b] - склеїти списки.

Пастка: числові ключі, які насправді ідентифікатори ([15 => 'Київ']), array_merge перенумерує й загубить. Для таких масивів потрібен + або array_replace.

Докладніше в документації: array_merge

Через usort і оператор <=> («космічний корабель»), який повертає -1, 0 або 1. Масиви теж можна порівнювати через <=> - поелементно, зліва направо, і це дає сортування за кількома полями одним рядком:

usort($users, fn (array $a, array $b): int =>
    [$b['score'], $a['name']] <=> [$a['score'], $b['name']]
);

Тут спершу за score за спаданням (тому $b і $a поміняні місцями), а за однакового рахунку - за name за зростанням.

Що ще варто знати:

  • usort скидає ключі. Щоб зберегти їх, потрібен uasort.
  • З PHP 8.0 сортування стабільне: рівні за критерієм елементи лишаються в початковому порядку. На цьому можна будувати сортування в кілька проходів.
  • Рядки <=> порівнює побайтово, тож «Ярослав» і «Андрій» стануть не за абеткою. Для тексту людською мовою - Collator з розширення intl.

У Laravel колекції роблять те саме читабельніше: collect($users)->sortBy([['score', 'desc'], ['name', 'asc']]).

Докладніше в документації: usort

Масиви в PHP передаються за значенням, але копіюються ліниво - за принципом copy-on-write. Присвоєння чи передача в функцію не копіює дані, а лише збільшує лічильник посилань. Справжня копія з'являється в момент, коли одну з «копій» змінюють.

$a = range(1, 1_000_000);
$b = $a;        // пам'ять не виросла: обидві змінні дивляться на ті самі дані
$b[] = 1;       // тепер копія - і ще ~кілька десятків МБ

Що з цього випливає:

  • Передавати великий масив у функцію дешево, поки функція його не змінює. Передача за посиланням (&$items) «для швидкодії» зазвичай нічого не дає.
  • Посилання можуть, навпаки, спричинити копіювання: змішування посилань і звичайних змінних на ті самі дані змушує PHP розділяти масив раніше.
  • Класична пастка - foreach ($items as &$item) без unset($item) після циклу: змінна лишається посиланням на останній елемент, і наступний foreach за значенням його перезапише.

Об'єкти поводяться інакше: змінна зберігає ідентифікатор об'єкта, тож передача в функцію дає доступ до того самого об'єкта. Для копії потрібен clone.

Докладніше в документації: Масиви

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.