Представь, что у тебя есть логи заказов интернет-магазина в виде строк.
📥 Входные данные
У тебя есть массив строк:
$orders = [
"order_id=1001;user=alex;total=250.50;status=paid",
"order_id=1002;user=john;total=99.99;status=pending",
"order_id=1003;user=alex;total=15.00;status=paid",
"order_id=1004;user=maria;total=300;status=failed",
"order_id=1005;user=john;total=120;status=paid",
];
Твоя задача
1️⃣ Распарсить данные
Преобразовать каждую строку в ассоциативный массив:
[
'order_id' => 1001,
'user' => 'alex',
'total' => 250.50,
'status' => 'paid'
]
2️⃣ Отфильтровать оплаченные заказы
Оставить только заказы со статусом paid.
3️⃣ Посчитать статистику по пользователям
Сформировать массив вида:
[
'alex' => [
'orders' => 2,
'total_sum' => 265.50
],
'john' => [
'orders' => 1,
'total_sum' => 120.00
]
]
4️⃣ Найти пользователя с максимальным числом заказов
Вывести:
alex (2)
Первое решение (черновое)
<?php
$orders = [
"order_id=1001;user=alex;total=250.50;status=paid",
"order_id=1002;user=john;total=99.99;status=pending",
"order_id=1003;user=alex;total=15.00;status=paid",
"order_id=1004;user=maria;total=300;status=failed",
"order_id=1005;user=john;total=120;status=paid",
];
$result = array_map(function($order) {
$orderDetails = explode(";", $order);
$keys = [];
$values = [];
foreach ($orderDetails as $item) {
$item = explode("=", $item);
$keys[] = $item[0];
$values[] = $item[1];
}
$orderAssoc = array_combine($keys, $values);
return $orderAssoc;
}, $orders);
// Оставить только заказы со статусом paid.
$orderDetailsFiltered = array_filter($result, function($arr) {
return $arr['status'] == 'paid';
});
// Посчитать статистику по пользователям
$ordersByUsers = [];
foreach($orderDetailsFiltered as $order) {
$user = $order['user'];
$alreadyInArray = in_2d_array($user, $orderDetailsFiltered);
if ($alreadyInArray) {
$ordersByUsers[$user] = [
'orders' => $ordersByUsers[$user]['orders'] + 1,
'total_sum' => $ordersByUsers[$user]['total_sum'] + $order['total']
];
} else {
$ordersByUsers[$user] = [
'orders' => 1,
'total_sum' => $order['total']
];
}
}
// Найти пользователя с максимальной суммой заказов
uasort($ordersByUsers, function($a, $b) {
return $b['orders'] <=> $a['orders'];
});
$topUser = array_key_first($ordersByUsers);
$topOrders = $ordersByUsers[$topUser]['orders'];
$result = $topUser . '(' . $topOrders . ')';
function in_2d_array($needle, $haystack) {
foreach($haystack as $element) {
if(in_array($needle, $element))
return true;
}
return false;
}
print_r($result);
// Output
alex(2)
Важные замечания:
- Используем uasort, а не usort чтобы сохранить ключи массива. Вот так выглядит массив после сортировки с помощью usort. Он ПЕРЕИНДЕКСИРУЕТ ключи в 0, 1, 2 и возвращает true / false.
Array
(
[0] => Array
(
[orders] => 2
[total_sum] => 265.5
)
[1] => Array
(
[orders] => 1
[total_sum] => 120
)
)
- uasort сохраняет ключи.
Array
(
[alex] => Array
(
[orders] => 2
[total_sum] => 265.5
)
[john] => Array
(
[orders] => 1
[total_sum] => 120
)
)
- Методы сортировки возвращают true / false и меняют массив, поэтому не надо присваивать результат в переменную.
$result = uasort($ordersByUsers, function($a, $b) {
return $b['orders'] <=> $a['orders'];
});
// Output
1
- array_combine() формирует массив с ключей и значений
$orderAssoc = array_combine($keys, $values);
Рефакторинг кода.
- Можно получать значения с помощью деструктурирующего присваивания массива – Array destructuring (деструктуризация массива).
$orderDetails = explode(";", $order);
$keys = [];
$values = [];
foreach ($orderDetails as $item) {
// $item = explode("=", $item);
/*Получаем массив ["order_id", "1001"]*/
/*Можем сразу положить в переменные*/
[$key, $value] = explode("=", $item, 2);
// $keys[] = $item[0];
// $values[] = $item[1];
$keys[] = $key;
$values[] = $value;
}
Говорим PHP: «Возьми 0-й элемент массива → положи в $key. Возьми 1-й элемент массива → положи в $value».
- Я каждый раз пересобираю массив пользователя.
$ordersByUsers[$user] = [
'orders' => $ordersByUsers[$user]['orders'] + 1,
'total_sum' => $ordersByUsers[$user]['total_sum'] + $order['total']
];
Можно обновлять существующие значения, это чище:
$ordersByUsers[$user]['orders']++;
$ordersByUsers[$user]['total_sum'] += (float)$order['total'];
- Нет смысла в функции in_2d_array.
$ordersByUsers = [];
foreach($orderDetailsFiltered as $order) {
$user = $order['user'];
$alreadyInArray = in_2d_array($user, $orderDetailsFiltered);
if ($alreadyInArray) {
$ordersByUsers[$user]['orders']++;
$ordersByUsers[$user]['total_sum'] += (float)$order['total'];
} else {
$ordersByUsers[$user] = [
'orders' => 1,
'total_sum' => $order['total']
];
}
}
Проверять нужно $ordersByUsers, а не $orderDetailsFiltered.
И вообще in_2d_array() здесь не нужен.
ПРАВИЛЬНЫЙ и ПРОСТОЙ алгоритм
- Берём заказ
- Смотрим пользователя
- Если пользователя ещё нет в
$ordersByUsers— создаём - Увеличиваем счётчик и сумму
То есть ситуация такая:
// Пустой массив куда будем записывать
$ordersByUsers = [];
// Массив, по которому проходимся циклом.
Array
(
[0] => Array
(
[order_id] => 1001
[user] => alex
[total] => 250.50
[status] => paid
)
[2] => Array
(
[order_id] => 1003
[user] => alex
[total] => 15.00
[status] => paid
)
[4] => Array
(
[order_id] => 1005
[user] => john
[total] => 120
[status] => paid
)
)
// Из пустого массива надо сделать такой
[
'alex' => [
'orders' => 2,
'total_sum' => 265.50
],
'john' => [
'orders' => 1,
'total_sum' => 120.00
]
]
Просто будем проверять есть ли в пустом массиве уже элемент с таким именем используя isset.
$ordersByUsers = [];
foreach($orderDetailsFiltered as $order) {
$user = $order['user'];
// $alreadyInArray = in_2d_array($user, $orderDetailsFiltered);
$alreadyInArray = isset($ordersByUsers[$user]);
if ($alreadyInArray) {
$ordersByUsers[$user]['orders']++;
$ordersByUsers[$user]['total_sum'] += (float)$order['total'];
} else {
$ordersByUsers[$user] = [
'orders' => 1,
'total_sum' => $order['total']
];
}
}
Теперь решение задачи выглядит так.
<?php
$orders = [
"order_id=1001;user=alex;total=250.50;status=paid",
"order_id=1002;user=john;total=99.99;status=pending",
"order_id=1003;user=alex;total=15.00;status=paid",
"order_id=1004;user=maria;total=300;status=failed",
"order_id=1005;user=john;total=120;status=paid",
];
$result = array_map(function($order) {
$orderDetails = explode(";", $order);
$keys = [];
$values = [];
foreach ($orderDetails as $item) {
[$key, $value] = explode("=", $item, 2);
$keys[] = $key;
$values[] = $value;
}
$orderAssoc = array_combine($keys, $values);
return $orderAssoc;
}, $orders);
// Оставить только заказы со статусом paid.
$orderDetailsFiltered = array_filter($result, function($arr) {
return $arr['status'] == 'paid';
});
// Посчитать статистику по пользователям
$ordersByUsers = [];
foreach($orderDetailsFiltered as $order) {
$user = $order['user'];
// $alreadyInArray = in_2d_array($user, $orderDetailsFiltered);
$alreadyInArray = isset($ordersByUsers[$user]);
if ($alreadyInArray) {
$ordersByUsers[$user]['orders']++;
$ordersByUsers[$user]['total_sum'] += (float)$order['total'];
} else {
$ordersByUsers[$user] = [
'orders' => 1,
'total_sum' => (float)$order['total']
];
}
}
// Найти пользователя с максимальной суммой заказов
uasort($ordersByUsers, function($a, $b) {
return $b['orders'] <=> $a['orders'];
});
$topUser = array_key_first($ordersByUsers);
$topOrders = $ordersByUsers[$topUser]['orders'];
$result = $topUser . '(' . $topOrders . ')';
print_r($result);
Найти ТОП-пользователя БЕЗ сортировки (O(n))
Плохой вариант (то, что было):
uasort($ordersByUsers, ...); // O(n log n)
$topUser = array_key_first($ordersByUsers);
Работает, но лишняя работа.
Правильный вариант: один проход
Идея:
- идём по массиву
- храним текущий максимум
- сравниваем
$topUser = null;
$maxOrders = 0;
foreach ($ordersByUsers as $user => $stats) {
if ($stats['orders'] > $maxOrders) {
$maxOrders = $stats['orders'];
$topUser = $user;
}
}
echo $topUser . '(' . $maxOrders . ')';
Финальное решение
<?php
$orders = [
"order_id=1001;user=alex;total=250.50;status=paid",
"order_id=1002;user=john;total=99.99;status=pending",
"order_id=1003;user=alex;total=15.00;status=paid",
"order_id=1004;user=maria;total=300;status=failed",
"order_id=1005;user=john;total=120;status=paid",
];
$result = array_map(function($order) {
$orderDetails = explode(";", $order);
$keys = [];
$values = [];
foreach ($orderDetails as $item) {
[$key, $value] = explode("=", $item, 2);
$keys[] = $key;
$values[] = $value;
}
$orderAssoc = array_combine($keys, $values);
return $orderAssoc;
}, $orders);
// Оставить только заказы со статусом paid.
$orderDetailsFiltered = array_filter($result, function($arr) {
return $arr['status'] == 'paid';
});
// Посчитать статистику по пользователям
$ordersByUsers = [];
foreach($orderDetailsFiltered as $order) {
$user = $order['user'];
// $alreadyInArray = in_2d_array($user, $orderDetailsFiltered);
$alreadyInArray = isset($ordersByUsers[$user]);
if ($alreadyInArray) {
$ordersByUsers[$user]['orders']++;
$ordersByUsers[$user]['total_sum'] += (float)$order['total'];
} else {
$ordersByUsers[$user] = [
'orders' => 1,
'total_sum' => (float)$order['total']
];
}
}
// Найти пользователя с максимальной суммой заказов
$topUser = null;
$maxOrders = 0;
foreach ($ordersByUsers as $user => $stats) {
if ($stats['orders'] > $maxOrders) {
$maxOrders = $stats['orders'];
$topUser = $user;
}
}
$result = $topUser . '(' . $maxOrders . ')';
print_r($result);