PHP数组:分组,总和

I'm trying to print out shopping cart orders, however, instead of displaying all products from that order I want to group the orders by the order ID so that I can display the sum of the order and link to a detail page.

Instead of this

---------------------------------------
date        order number        total
---------------------------------------
today        1234WEDK             600
---------------------------------------
today        1234WEDK             500
---------------------------------------

I'd like to display this

---------------------------------------
date        order number        total
---------------------------------------
today        1234WEDK            1100
---------------------------------------

Here's a sample array

array:2 [▼
  0 => array:9 [▼
    "id" => 57
    "order_id" => 51
    "order_hash" => "1234WEDK"
    "price" => 600
  ]
  1 => array:9 [▼
    "id" => 58
    "order_id" => 51
    "order_hash" => "1234WEDK"
    "price" => 500
  ]
]

Assuming you are getting your data from a database, you should make it to do all the calculations for you

SELECT order_hash, date, sum(price) as total FROM cart GROUP BY order_hash
$data = [
  [
    "id" => 57,
    "order_id" => 51,
    "order_hash" => "1234WEDK",
    "price" => 600
  ],
  [
    "id" => 58,
    "order_id" => 51,
    "order_hash" => "1234WEDK",
    "price" => 500
  ],
  [
    "id" => 59,
    "order_id" => 1111,
    "order_hash" => "1234ABCD",
    "price" => 200
  ]
];

$result = array_reduce($data, function($acc, $x) {
  if (!array_key_exists($x['order_hash'], $acc)) {
    $acc[$x['order_hash']] = 0;
  }
  $acc[$x['order_hash']] += $x['price'];
  return $acc;
}, []);

echo json_encode($result, JSON_PRETTY_PRINT), PHP_EOL;

Output

{
    "1234WEDK": 1100,
    "1234ABCD": 200
}

That solution is mostly crap tho because the function is totally tangled up with multiple intentions and responsibilities. It's looking up object properties, grouping, and summing values all in one. Blech.

Functional Programming in PHP can be pretty verbose, but that doesn't mean it should be entirely dismissed.

What if I told you … the entire solution could be reduced to this ?

// your reusable computation
$sumOrdersByHash = comp (map (function($xs) {
  return [
    'hash' => prop ('order_hash') (head ($xs)),
    'sum'  => sum (map (prop('price')) ($xs))
  ];
})) (group_by (prop ('order_hash')));

// run computation
$result = $sumOrdersByHash($data);

All you need are these reusable prerequisites. Bwahahahaha …

function id ($x) { return $x; }
function head ($xs) { return $xs[0]; }
function tail ($xs) { return array_slice($xs, 1); }

function foldl (callable $f) {
  return function ($acc) use ($f) {
    return function ($xs) use ($f, $acc) {
      if (empty($xs))
        return $acc;
      else
        return foldl ($f) (call_user_func($f, $acc, head($xs))) (tail ($xs));
    };
  };
}

function map (callable $f) {
  return foldl (function ($acc, $x) use ($f) {
    return array_merge($acc, [call_user_func($f, $x)]);
  }) ([]);
}

function sum ($xs) {
  return foldl (function ($x, $y) { return $x + $y; }) (0) ($xs);
}

function prop ($k) {
  return function ($arr) use ($k) {
    return $arr[$k];
  };
};

function group_by (callable $f) {
  return foldl (function ($acc, $x) use ($f) {
    $key = call_user_func($f, $x);
    if (array_key_exists($key, $acc))
      array_push($acc[$key], $x);
    else
      $acc[$key] = [$x];
    return $acc;
  }) ([]);
}

function comp (callable $f) {
  return function (callable $g) use ($f) {
    return function ($x) use ($f, $g) {
      return call_user_func($f, call_user_func($g, $x));
    };
  };
}

Put it all together and here's what you get

$result = $sumOrdersByHash($data);
echo json_encode($result, JSON_PRETTY_PRINT), PHP_EOL;

// =>    
[
    {
        "hash": "1234WEDK",
        "sum": 1100
    },
    {
        "hash": "1234ABCD",
        "sum": 200
    }
]