Объединение двух массивов с объединением дубликатов по одному из полей

У меня проблема с объединением массивов по определенному сценарию. Поиск похожих случаев здесь не дал результатов. Чтобы четко понять, каковы мои требования, посмотрите на следующий пример:

Первый массив:

Array
(
    [0] => stdClass
        (
            [call_date] => 2013-10-22 00:00:00
            [first_amount] => 10
        )

    [1] => stdClass
        (
            [call_date] => 2013-10-23 00:00:00
            [first_amount] => 20
        )
)

Второй массив:

Array
(
    [0] => stdClass
        (
            [call_date] => 2013-10-22 00:00:00
            [second_amount] => 30
        )

    [1] => stdClass
        (
            [call_date] => 2013-10-24 00:00:00
            [second_amount] => 40
        )
)

Что мне нужно на выходе:

Array
(
    [0] => stdClass
        (
            [call_date] => 2013-10-22 00:00:00
            [first_amount] => 10
            [second_amount] => 30
        )

    [1] => stdClass
        (
            [call_date] => 2013-10-23 00:00:00
            [first_amount] => 20
        )

    [2] => stdClass
        (
            [call_date] => 2013-10-24 00:00:00
            [second_amount] => 40
        )
)

Итак, как вы видите, слияние происходит по call_date. Элементы из первого и второго массива, находящиеся под датой 2013-10-22 00:00:00, были объединены, элемент из второго массива под датой 2013-10-24 00:00:00 был добавлен.

Перепробовал множество комбинаций array_merge, array_udiff, array_merge_recursive, array_map, но ничего не помогло :(.

Будем признательны за решение этой проблемы!


person Riddick    schedule 29.10.2013    source источник
comment
А для каждого, с тестовым условием? php.net/manual/en/control-structures.foreach.php . Все эти функции просто foreach.   -  person Rémi Benoit    schedule 29.10.2013
comment
Функции массива здесь не будут особенно полезны, так как у вас не есть массивы внутри ваших массивов, а объекты. Таким образом, вам придется добавить атрибуты к существующему объекту или создать новый объект, независимо от того, существует ли он с call_date в вашем первом массиве.   -  person CBroe    schedule 29.10.2013
comment
Просто foreach с использованием call_date в качестве ключа нового массива, если элемент не существует, добавьте, если он есть, добавьте столбец *_amount (или просто $newarray[$item->call_date] = (object)array_merge(get_object_vars($newarray[$item->call_date]),get_object_vars($item)); (вы говорите о слиянии object здесь.   -  person Wrikken    schedule 29.10.2013
comment
@ Реми, спасибо за ответ. Итак, каков наиболее оптимальный способ использования foreach? Должен ли я использовать вложенные foreach-s?   -  person Riddick    schedule 29.10.2013
comment
Нет, только 1 foreach подряд для каждого массива, который вы хотите добавить к нему (так что foreach($array1 as $item){..}foreach($array2 as $item){..} и т. д.)   -  person Wrikken    schedule 29.10.2013
comment
@CBroe, я могу преобразовывать объекты в массивы, это не главная проблема, вы можете рассматривать массив вместо stdClass.   -  person Riddick    schedule 29.10.2013
comment
@Riddick Нет оптимального способа для такой базовой языковой функции. Предложите какой-нибудь код, тогда мы сможем поговорить подробнее!   -  person Rémi Benoit    schedule 29.10.2013
comment
Что ж, тогда я не вижу в этом никакой реальной проблемы… вы просто проверяете, есть ли уже элемент для call_date, и добавляете к нему новое значение или создаете новое. Вы действительно должны попробовать это сами, потому что на данный момент ваш вопрос в основном просто просит код.   -  person CBroe    schedule 29.10.2013
comment
А как насчет: array_unique? array_unique($array1 + $array2 + $array3) php.net/manual /en/function.array-unique.php   -  person BENARD Patrick    schedule 29.10.2013
comment
@ Jahnux73, уже пробовал, не помогло, так как удаляет только полные повторяющиеся записи.   -  person Riddick    schedule 29.10.2013


Ответы (4)


Простой сценарий:

  1. изменить ключи массива
  2. объединить рекурсивно
  3. отображать объединенные элементы обратно в объект (stdClass)

Пример:

//change key
$workFirstArray = array_combine(
    array_map(function($object) { return $object->call_date;}, $firstArray), $firstArray
);
$workSecondArray = array_combine(
    array_map(function($object) { return $object->call_date;}, $secondArray), $secondArray
);

//map merged elements back to StdClass
$result = array_map(function($element) {
        if(is_array($element)) {
            $element['call_date'] = end($element['call_date']);
            $element=(object)$element;
        }
        return $element;
    },
    array_merge_recursive($workFirstArray, $workSecondArray)
);

выход:

Array
(
    [0] => stdClass Object
        (
            [call_date] => 2013-10-22 00:00:00
            [first_amount] => 10
            [second_amount] => 40
        )

    [1] => stdClass Object
        (
            [call_date] => 2013-10-23 00:00:00
            [second_amount] => 30
        )

    [2] => stdClass Object
        (
            [call_date] => 2013-10-24 00:00:00
            [second_amount] => 40
        )

)
person ziollek    schedule 29.10.2013
comment
ВАУ, это сработало, и довольно быстро! Большое спасибо! Добавил только одну вещь, чтобы завершить ваше решение: $result = array_values($result); - person Riddick; 29.10.2013

$arr1 = array(  array(  'call_date'    => '2013-10-22 00:00:00',
                        'first_amount' => 10),
                array(  'call_date'    => '2013-10-23 00:00:00',
                        'first_amount' => 20));

$arr2 = array(  array(  'call_date'     => '2013-10-22 00:00:00',
                        'second_amount' => 30),
                array(  'call_date'     => '2013-10-24 00:00:00',
                        'second_amount' => 40));

$arr_merged = array_merge($arr1, $arr2);

$arr_result = array();
foreach ($arr_merged as $arr) {

    if ( ! isset($arr_result[$arr['call_date']]))
        $arr_result[$arr['call_date']] = array();

    $arr_result[$arr['call_date']] += $arr;

}

$arr_result = array_values($arr_result);
  1. Определите соответствующие массивы, такие как $arr1 и $arr2
  2. array_merge() их
  3. Создайте новый массив с логикой объединения на 'call_date'
  4. Удалите дату как ключ через array_values()
person Robin Castlin    schedule 29.10.2013
comment
Не работал. Для больших массивов есть ошибки: Notice: Undefined index: 2013-06-26 03:00:00 ... Fatal error: Unsupported operand types ... - person Riddick; 29.10.2013
comment
Попробуй. Возможно, сначала нужно определить значение как array. - person Robin Castlin; 29.10.2013
comment
Ага, теперь работает. Немного медленнее, чем решение @ziollek (от 25-30 мс до 40-45 мс), но тоже работает. Это альтернативное решение! Благодарю вас! - person Riddick; 29.10.2013

$result = $first;
$amounts = array('first_amount','second_amount');
foreach ($first as $fKey => $f) {
    foreach ($second as $sKey => $s) {
        if ($f['call_date'] == $s['call_date']) {
            foreach ($amounts as $amount) {
                if (!isset($f[$amount]) && isset($s[$amount]))
                    $result[$fKey][$amount] = $s[$amount];
            }
            unset($second[$sKey]);
            break;
        }
    }
}
$result = array_merge($result, $second);

если у вас есть больше ключей, добавьте массив сумм, если вы хотите заменить существующие ключи, удалите !isset($f[$amount]) && из условия, и у вас есть более одного равного call_date, удалите также break;.

person Anees Sadeek    schedule 29.10.2013

person    schedule
comment
Ваше решение предполагает фиксированные индексы (как second_amount), но оно должно быть независимым от них. - person Riddick; 29.10.2013