首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 二維陣列依鍵名分組加總:高效能分組聚合演算法與實戰優化

     編輯:PHP 相關知識

前言

處理訂單明細、銷售報表、日誌統計時,常需「依特定欄位分組,並對數值欄位求和」。雖資料庫 GROUP BY 為首選,但資料已在 PHP 陣列中(如多表 JOIN 後合併、API 彙總、快取資料)時,需高效的記憶體內分組演算法。本文提供 O(n) 時間複雜度的標準實作與進階優化。

標準分組加總演算法

/**
 * 依指定鍵分組,數值欄位自動加總
 * @param array  $rows     來源二維陣列
 * @param string $groupKey 分組鍵名
 * @return array 分組後陣列(索引重排為 0,1,2...)
 */
function groupSum(array $rows, string $groupKey): array {
    $map = []; // 分組鍵 => 累加陣列
    foreach ($rows as $row) {
        $key = $row[$groupKey] ?? null;
        if ($key === null) continue; // 無分組鍵跳過

        if (!isset($map[$key])) {
            $map[$key] = $row; // 首筆直接複製
            continue;
        }
        // 後續筆:數值欄位累加
        foreach ($row as $k => $v) {
            if ($k === $groupKey) continue;
            if (is_numeric($v)) {
                $map[$key][$k] = ($map[$key][$k] ?? 0) + $v;
            }
            // 非數值欄位(如日期、狀態)保留首筆或依業務邏輯處理
        }
    }
    return array_values($map); // 重排數值索引
}

測試資料與結果

 13, 'team_price' => 88.00, 'satopay_price' => 85.00, 'origin' => 440, 'gain' => 14.45, 'quantity' => 5],
    ['group_id' => 13, 'team_price' => 12.00, 'satopay_price' => 11.00, 'origin' => 36,   'gain' => 2.76,  'quantity' => 3],
    ['group_id' => 14, 'team_price' => 4.99,  'satopay_price' => 4.60,  'origin' => 4.99, 'gain' => 0.31915,'quantity' => 1],
    ['group_id' => 14, 'team_price' => 12.00, 'satopay_price' => 11.00, 'origin' => 24,   'gain' => 1.84,  'quantity' => 2],
    ['group_id' => 15, 'team_price' => 13.00, 'satopay_price' => 11.00, 'origin' => 24,   'gain' => 1.84,  'quantity' => 2],
];

$result = groupSum($data, 'group_id');
/*
[
  ['group_id'=>13, 'team_price'=>100.00, 'satopay_price'=>96.00, 'origin'=>476, 'gain'=>17.21, 'quantity'=>8],
  ['group_id'=>14, 'team_price'=>16.99,  'satopay_price'=>15.60, 'origin'=>28.99, 'gain'=>2.15915,'quantity'=>3],
  ['group_id'=>15, 'team_price'=>13.00,  'satopay_price'=>11.00, 'origin'=>24,    'gain'=>1.84,   'quantity'=>2],
]
*/

進階優化版:支援自定義聚合規則

function groupAggregate(
    array $rows,
    string $groupKey,
    array $sumFields = [],      // 需加總欄位,空則自動偵測數值欄位
    array $firstFields = [],    // 保留首筆欄位
    array $lastFields = [],     // 保留末筆欄位
    callable $customReducer = null // 自定義 reducer(key, carry, row)
): array {
    $map = [];
    foreach ($rows as $row) {
        $key = $row[$groupKey] ?? null;
        if ($key === null) continue;

        if (!isset($map[$key])) {
            $map[$key] = $row;
            continue;
        }
        $carry = &$map[$key];
        foreach ($row as $k => $v) {
            if ($k === $groupKey) continue;
            if ($customReducer) {
                $carry[$k] = $customReducer($k, $carry[$k] ?? null, $v);
                continue;
            }
            if ($sumFields && in_array($k, $sumFields, true)) {
                $carry[$k] = ($carry[$k] ?? 0) + $v;
            } elseif ($lastFields && in_array($k, $lastFields, true)) {
                $carry[$k] = $v; // 覆蓋為最新
            } // firstFields 無需處理,保留首筆
        }
    }
    return array_values($map);
}

// 使用範例
$result = groupAggregate($data, 'group_id',
    sumFields: ['team_price', 'satopay_price', 'origin', 'gain', 'quantity'],
    lastFields: ['updated_at']
);

效能分析與大資料應對

資料筆數時間複雜度記憶體建議
< 10,000O(n)~2x 原陣列直接用上述函式
10,000 ~ 1,000,000O(n)較大分批處理、Generator 串流、或下推 SQL
> 1,000,000O(n)極大ClickHouse、Druid、資料倉儲預聚合

分批處理模式(避免記憶體峰值)

function groupSumBatch(Iterator $source, string $groupKey, int $batchSize = 10000): Generator {
    $map = [];
    $count = 0;
    foreach ($source as $row) {
        $key = $row[$groupKey] ?? null;
        if ($key === null) continue;
        if (!isset($map[$key])) {
            $map[$key] = $row;
        } else {
            foreach ($row as $k => $v) {
                if ($k === $groupKey) continue;
                if (is_numeric($v)) $map[$key][$k] = ($map[$key][$k] ?? 0) + $v;
            }
        }
        if (++$count % $batchSize === 0) {
            yield $map;
            $map = []; // 釋放記憶體,下游自行合併多批次結果
        }
    }
    if ($map) yield $map;
}

// 合併多批次結果
$final = [];
foreach (groupSumBatch($largeIterator, 'group_id') as $batch) {
    foreach ($batch as $key => $row) {
        if (!isset($final[$key])) $final[$key] = $row;
        else foreach ($row as $k => $v) if ($k !== 'group_id' && is_numeric($v)) $final[$key][$k] += $v;
    }
}
$result = array_values($final);

替代方案:Collection 管道(Laravel/Illuminate\Support)

use Illuminate\Support\Collection;

$collect = collect($data);
$grouped = $collect->groupBy('group_id')
    ->map(fn($rows) => $rows->reduce(function($carry, $row) {
        foreach ($row as $k => $v) {
            if ($k === 'group_id') continue;
            $carry[$k] = ($carry[$k] ?? 0) + (is_numeric($v) ? $v : 0);
        }
        return $carry;
    }, ['group_id' => $rows->first()['group_id']]))
    ->values()
    ->all();

常見錯誤與除錯

  1. 浮點數精度:金額加總建議存「分」為整數,或用 bcadd()、decimal 型別。
  2. 分組鍵為陣列/物件:無法作為陣列索引,需序列化或轉字串。
  3. 欄位名稱大小寫不一:資料庫回傳可能為 Group_ID,統一 strtolower() 或對應映射表。
  4. 遺漏非數值欄位合併邏輯:如 status、created_at 需決定取首筆、末筆、最頻值等。

結語

PHP 記憶體內分組加總核心是「雜湊表累加」,時間 O(n)、空間 O(k)(k 為分組數)。掌握標準演算法、自定義聚合規則、分批處理與下推 SQL 三層策略,即能應對從小型報表到百萬筆資料的各類彙總需求。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved