timgavin timgavin - 2 months ago 21
PHP Question

PHP Arrays: Grouping, Sum

I know this is going to be easy for a lot of you guys, but my brain is mush and I can't wrap my head around this right now. Arrays were never my strong suit.

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
]
]

Answer

This would've been easier if I was a boy

$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
    }
]