Вот вариант, который будет работать при любом порядке категорий...
Оптимизировать/укоротить код - это уже Вам задание :)
<?php
$TEST_DATA = 'Продукт 1; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 2; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 3; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 4; Воздуховоды/Круглые/Нержавеющие;
Продукт 5; Воздуховоды/Круглые/Нержавеющие;
Продукт 6; Воздуховоды/Круглые/Нержавеющие;';
$TEST_DATA2 = 'Продукт 1; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 2; Воздуховоды/Круглые/Нержавеющие;
Продукт 3; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 4; Воздуховоды/Круглые/Нержавеющие;
Продукт 5; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 6; Воздуховоды/Круглые/Нержавеющие;';
$arData = array_map(function($val) {
return array_filter(array_map('trim', explode(';', $val)));
}, preg_split('#[\r\n]#isu', $TEST_DATA, -1, PREG_SPLIT_NO_EMPTY));
/* Всё, что выше - к задаче не относится...имитация получения данных из CSV... */
$arRes = [];
foreach($arData as $data) {
$arCategories = array_filter(array_map('trim', explode('/', $data[1])));
$MAIN_CATEGORY = array_shift($arCategories);
$HASH = md5(implode('/', $arCategories));
$arRes[$MAIN_CATEGORY][$HASH][] = [
'product_name' => $data[0],
'category' => $data[1],
'category_items' => $arCategories
];
}
$HASH = false;
foreach($arRes as $main_category => $arSubCategories) {
echo $main_category."\r\n";
foreach($arSubCategories as $h => $arItems)
foreach($arItems as $item) {
if($HASH != $h) {
$HASH = $h;
foreach($item['category_items'] as $k => $category) {
$k++;
echo str_repeat('!', $k).$category."\r\n";
}
}
echo $item['product_name'].';'.$item['category'].';'."\r\n";
}
}
/* RESULT:
Воздуховоды
!Прямоугольные
!!Оцинкованные
Продукт 1;Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 2;Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 3;Воздуховоды/Прямоугольные/Оцинкованные;
!Круглые
!!Нержавеющие
Продукт 4;Воздуховоды/Круглые/Нержавеющие;
Продукт 5;Воздуховоды/Круглые/Нержавеющие;
Продукт 6;Воздуховоды/Круглые/Нержавеющие;
*/
UPD:
Вариант с одним циклом
с учётом того, что категории уже отсортированы:
<?php
$TEST_DATA = 'Продукт 1; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 2; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 3; Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 4; Воздуховоды/Круглые/Нержавеющие;
Продукт 5; Воздуховоды/Круглые/Нержавеющие;
Продукт 6; Воздуховоды/Круглые/Нержавеющие;';
$arData = array_map(function($val) {
return array_filter(array_map('trim', explode(';', $val)));
}, preg_split('#[\r\n]#isu', $TEST_DATA, -1, PREG_SPLIT_NO_EMPTY));
/* Всё, что выше - к задаче не относится...имитация получения данных из CSV... */
$VAR = false;
$VAR2 = false;
foreach($arData as $data) {
$arCategories = array_filter(array_map('trim', explode('/', $data[1])));
$MAIN_CATEGORY = array_shift($arCategories);
if($VAR !== $MAIN_CATEGORY) {
$VAR = $MAIN_CATEGORY;
echo $MAIN_CATEGORY."\r\n";
}
$CATEGORIES = implode('/', $arCategories);
if($VAR2 !== $CATEGORIES) {
foreach($arCategories as $k => $category) {
$k++;
echo str_repeat('!', $k).$category."\r\n";
}
$VAR2 = $CATEGORIES;
}
echo $data[0].';'.$data[1].';'."\r\n";
}
/* RESULT:
Воздуховоды
!Прямоугольные
!!Оцинкованные
Продукт 1;Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 2;Воздуховоды/Прямоугольные/Оцинкованные;
Продукт 3;Воздуховоды/Прямоугольные/Оцинкованные;
!Круглые
!!Нержавеющие
Продукт 4;Воздуховоды/Круглые/Нержавеющие;
Продукт 5;Воздуховоды/Круглые/Нержавеющие;
Продукт 6;Воздуховоды/Круглые/Нержавеющие;
*/