Filtering an associative array
Glossary overview

Filtering an associative array

Задача

У тебя есть массив автомобилей в таком виде:

$cars = [
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Ford', 'model' => 'Fusion', 'year' => 2018],
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Toyota', 'model' => 'Corolla', 'year' => 2019],
    ['brand' => 'Ford', 'model' => 'Escape', 'year' => 2021],
    ['brand' => 'Toyota', 'model' => 'Camry', 'year' => 2018],
];

Твоя цель:

  1. ❌ Удали дубликаты машин с одинаковыми 'model' и 'year'.
  2. 🔎 Оставь только автомобили Ford.
  3. 🔄 Преобразуй каждый оставшийся элемент в строку такого формата:
    "Model: Mustang (2020)".
  4. 📦 Верни результат как массив строк.

Пример результата:

[
    "Model: Mustang (2020)",
    "Model: Fusion (2018)",
    "Model: Escape (2021)"
]

Мои ошибки

<?php
	$cars = [
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Ford', 'model' => 'Fusion', 'year' => 2018],
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Toyota', 'model' => 'Corolla', 'year' => 2019],
    ['brand' => 'Ford', 'model' => 'Escape', 'year' => 2021],
    ['brand' => 'Toyota', 'model' => 'Camry', 'year' => 2018],
];

// Удали дубликаты машин с одинаковыми 'model' и 'year'.


$result = array_reduce($cars, function($newArr, $car) {
	if (!in_array($car['model'], $newArr) || !in_array($car['model'], $newArr)) {
		$newArr[] = $car;
	}
	
	
}, []);

print_r($result);


ошибка:
Fatal error: Uncaught TypeError: in_array(): Argument #2 ($haystack) must be of type array, null given in /home/user/scripts/code.php:15
Stack trace:
#0 /home/user/scripts/code.php(15): in_array('Fusion', NULL)
#1 [internal function]: {closure}(NULL, Array)
#2 /home/user/scripts/code.php(14): array_reduce(Array, Object(Closure), Array)
#3 {main}
  thrown in /home/user/scripts/code.php on line 15

❌ Ошибки:

  1. Внутри array_reduce ты не возвращаешь результат в конце функции — без return $newArr ничего не будет сохраняться.
  2. in_array($car['model'], $newArr) — это неправильно, потому что $newArr содержит массивы, а ты сравниваешь его со строкой ('Mustang' и т.д.).
  3. Ты сравниваешь только model, но по задаче нужно сравнивать пару model + year.

in_array($car[‘model’], …) — ❌ не сработает с массивом массивов

Ты сравниваешь строку ($car['model']) со списком массивов, а in_array не умеет искать 'Mustang' в массиве типа:

[
  ['model' => 'Mustang'],
  ['model' => 'Fusion']
]

in_array("abc", ["abc", "def"])true

in_array отлично сравнивает строку со строками, число с числами, булевое с булевым и т.д.

Но! — in_array(“abc”, [[“model” => “abc”]]) → false

Если ты сравниваешь строку с массивамиin_array не будет “залазить внутрь” массивов:

$car = "Mustang";
$arr = [
  ["model" => "Mustang"],
  ["model" => "Fusion"]
];

in_array($car, $arr); // ❌ false

Потому что in_array сравнивает **$car == [“model” => “Mustang”]**, а это сравнение строки с массивом → false`.

🔧 Чтобы сравнить "Mustang" с ['model' => 'Mustang'] внутри массива:

Используй array_column() (читай здесь):

in_array("Mustang", array_column($arr, 'model')) // ✅ true

Если мы хотим использовать in_array, то надо сделать так чтобы мы сравнивали строки.

Поэтому Делаем ключ для сравнения model + year

Ты хочешь сравнивать дубликаты по model и year, значит нужен уникальный ключ, например:

$key = $car['model'] . '_' . $car['year'];

Но in_array($key, $newArr) не сработает, потому что $newArr — массив машин (не строк), а ты сравниваешь со строкой.

Решение: введем вспомогательный массив $seenKeys, чтобы в нём хранить строки вроде Mustang_2020.

Но array_reduce принимает только один аккумулятор. Значит мы должны хранить и $newArr, и $seenKeys в одном массиве.

Используем аккумулятор с двумя массивами

$result = array_reduce($cars, function($acc, $car) {
	$key = $car['model'] . '_' . $car['year'];

	if (!in_array($key, $acc['seen'])) {
		$acc['seen'][] = $key;
		$acc['filtered'][] = $car;
	}

	return $acc;
}, ['seen' => [], 'filtered' => []]);

Получаем финальный результат

После array_reduce, результат будет в $result['filtered'].

print_r($result['filtered']);

Итоговый код

<?php
$cars = [
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Ford', 'model' => 'Fusion', 'year' => 2018],
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Toyota', 'model' => 'Corolla', 'year' => 2019],
    ['brand' => 'Ford', 'model' => 'Escape', 'year' => 2021],
    ['brand' => 'Toyota', 'model' => 'Camry', 'year' => 2018],
];

$result = array_reduce($cars, function($acc, $car) {
    $key = $car['model'] . '_' . $car['year'];

    if (!in_array($key, $acc['seen'])) {
        $acc['seen'][] = $key;
        $acc['filtered'][] = $car;
    }

    return $acc;
}, ['seen' => [], 'filtered' => []]);

print_r($result['filtered']);

Мое полное решение

<?php
	$cars = [
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Ford', 'model' => 'Fusion', 'year' => 2018],
    ['brand' => 'Ford', 'model' => 'Mustang', 'year' => 2020],
    ['brand' => 'Toyota', 'model' => 'Corolla', 'year' => 2019],
    ['brand' => 'Ford', 'model' => 'Escape', 'year' => 2021],
    ['brand' => 'Toyota', 'model' => 'Camry', 'year' => 2018],
];


$result = array_reduce($cars, function($newArr, $car) {
	
	if ($car['brand'] !== 'Ford') return $newArr;
	
	$key =  $car['model'] . '_' . $car['year'];
	
	if (!in_array($key, $newArr["keys"])) {
		$newArr["keys"][] = $key;
		$newArr["filtered"][] = $car;
	}
	
	return $newArr;

	
}, ["keys" => [], "filtered" => []]);

$filtered_arr = $result["filtered"];


$final = array_map(function($item) {
	
	return 'Model: ' . $item['model'] . '(' . $item['year'] . ')';
	
}, $filtered_arr);

// Вывод
Array
(
    [0] => Model: Mustang(2020)
    [1] => Model: Fusion(2018)
    [2] => Model: Escape(2021)
)

Как улучшить мой код

Раздели фильтрацию и удаление дубликатов

Сейчас всё происходит в одном array_reduce. Это ок, но можно упростить, сделав два прохода: сначала фильтруем только Ford, потом убираем дубликаты. Код станет проще для чтения.

// 1. Оставим только Ford
$only_fords = array_filter($cars, fn($car) => $car['brand'] === 'Ford');

// 2. Удалим дубликаты по model + year
$seen = [];
$unique_fords = [];

foreach ($only_fords as $car) {
    $key = $car['model'] . '_' . $car['year'];
    if (!in_array($key, $seen)) {
        $seen[] = $key;
        $unique_fords[] = $car;
    }
}

// 3. Преобразуем в строки
$formatted = array_map(
    fn($car) => "Model: {$car['model']}({$car['year']})",
    $unique_fords
);

print_r($formatted);

🧠 Альтернатива с array_reduce (если хочешь компактнее)

Если тебе всё-таки нравится array_reduce, можно просто чуть привести в порядок:

$result = array_reduce($cars, function($acc, $car) {
    if ($car['brand'] !== 'Ford') return $acc;

    $key = $car['model'] . '_' . $car['year'];

    if (!in_array($key, $acc['seen'])) {
        $acc['seen'][] = $key;
        $acc['filtered'][] = $car;
    }

    return $acc;
}, ['seen' => [], 'filtered' => []]);

$formatted = array_map(
    fn($car) => "Model: {$car['model']}({$car['year']})",
    $result['filtered']
);

print_r($formatted);

********

Используем сокращённый синтаксис для анонимных функций в PHP, появившийся с версии PHP 7.4. Он называется Arrow Function (стрелочная функция), и это аналог function(...) { ... }, но компактнее.

Идеально подходит для array_map, array_filter, usort и т.д.

// Обычная функция
$double = function($x) {
    return $x * 2;
};

// Стрелочная
$double = fn($x) => $x * 2;

Где будет Return.

Вариант 1.

$result = array_reduce($cars, function($acc, $car) {
    $key = $car['model'] . '_' . $car['year'];

    if (!in_array($key, $acc['seen'])) {
        $acc['seen'][] = $key;
        $acc['filtered'][] = $car;
    }

    return $acc;
}, ['seen' => [], 'filtered' => []]);

💡 Как работает:

  • В каждой итерации функция всегда возвращает $acc.
  • Даже если if не выполнился, array_reduce получит обновлённый $acc.

Это правильно и безопасно.

Вариант 2.

$result = array_reduce($cars, function($acc, $car) {
    $key = $car['model'] . '_' . $car['year'];

    if (!in_array($key, $acc['seen'])) {
        $acc['seen'][] = $key;
        $acc['filtered'][] = $car;
        
        return $acc;
    }

    
}, ['seen' => [], 'filtered' => []]);

Что произойдёт:

  • Если условие if НЕ выполнится, то функция ничего не вернёт.
  • А array_reduce ожидает, что твоя функция всегда возвращает новый аккумулятор.
  • В результате PHP передаст null в следующую итерацию как $acc, и это вызовет ошибку:
TypeError: Cannot access offset of type null

Вывод:

Возврат return $accЧто будет?
всегда в конце функциивсё работает
только внутри ifможет сломаться, если условие не выполнится