4

i'm trying to figure out how to sum certain values of a multi-dimensional array if they have similar dates.

Here's my array:

<?$myArray=array(

array(
        'year' => 2011,
        'month ' => 5,
        'day' => 13,
        'value' => 2
    ),
array(
        'year' => 2011,
        'month '=> 5,
        'day' => 14,
        'value' => 5
    ),
array(
        'year' => 2011,
        'month ' => 5,
        'day' => 13,
        'value' => 1
    ),
array(
        'year' => 2011,
        'month ' => 5,
        'day' => 14,
        'value' => 9
    )
);?>

here's how i'd like the output to look:

<?$output=array(

array(
        'year' => 2011,
        'month ' => 5,
        'day' => 13,
        'value' => 3 //the sum of 1+2
    ),
array(
        'year' => 2011,
        'month '=> 5,
        'day' => 14,
        'value' => 14 //the sum of 5+9
    )
);?>

Notice how the 4 sub-arrays were matched on year/month/day and then only the value was summed. I've seen other SO threads on this topic but can't find one where only the value is summed and not the year/month/day values too.

Thoughts?

Community
  • 1
  • 1
tim peterson
  • 23,653
  • 59
  • 177
  • 299
  • 3
    It's much simpler if you just use DateTime objects. – Jonathan Aug 27 '12 at 12:43
  • @MrAzulay thanks can you show me/send a link for how the array matching/summing would look like using DateTime objects? I'll update my question to include this possible alternate format for `$myArray`. – tim peterson Aug 27 '12 at 12:45

2 Answers2

4

It may be easiest to initially index your output array with a combination of the year/month/day:

Note: Your example array above has all its month keys with a trailing space. I'm just using month here with no trailing space.

// Initialize output array...
$out = array();

// Looping over each input array item
foreach ($myArray as $elem) {
  // Initialize a new element in the output keyed as yyyy-mm-dd if it doesn't already exist
  if (!isset($out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']])) {
    $out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']] = array(
      // Set the date keys...
      'year' => $elem['year'],
      'month' => $elem['month '],
      'day' => $elem['day'],
      // With the current value...
      'value' => $elem['value']
    );
  }
  // If it already exists, just add the current value onto it...
  else {
     $out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']]['value'] += $elem['value'];
  }
}

// Now your output array is keyed by date.  Use array_values() to strip off those keys if it matters:
$out = array_values($out);

Outputs (before calling array_values()):

array(2) {
  '2011-5-13' =>
  array(4) {
    'year' =>
    int(2011)
    'month' =>
    int(5)
    'day' =>
    int(13)
    'value' =>
    int(3)
  }
  '2011-5-14' =>
  array(4) {
    'year' =>
    int(2011)
    'month' =>
    int(5)
    'day' =>
    int(14)
    'value' =>
    int(14)
  }
}

Update:

To do the same thing with single-key dates (rather than 3-parts) it is easier without the concatenation:

$myArray=array(
      array(
            'date' => '2011-05-13',
            'value' => 2
        ),
    array(
            'date' => '2011-05-14',
            'value' => 5
        ),
        array(
            'date' => '2011-05-13',
            'value' => 7
        ),
    array(
            'date' => '2011-05-14',
            'value' => 3
        ),

);

   foreach ($myArray as $elem) {
      // Initialize a new element in the output if it doesn't already exist
      if (!isset($out[$elem['date']])) {
        $out[$elem['date'] = array(
          // Set the date keys...
          'date' => $elem['date'],
          // With the current value...
          'value' => $elem['value']
        );
      }
      else {
        $out[$elem['date']]['value'] += $elem['value'];
      }
    }
Michael Berkowski
  • 267,341
  • 46
  • 444
  • 390
  • -@Michael, thanks following @MrAzulay's suggestion i can reformat the date's like this `yyyy-mm-dd` if this makes it easier (the sub-array only contains 2 and not 4 key-value pairs). – tim peterson Aug 27 '12 at 12:48
  • 1
    @DainisAbols I fixed the double `[[` bracket already. copy/paste error – Michael Berkowski Aug 27 '12 at 12:49
  • 1
    I tested your code and got this `Parse error: syntax error, unexpected '[', expecting ']' in C:\data\test.php on line 40`. That's at `$out[[$elem['year'] . "-" . $elem['month'] . "-" . $elem['day']] = array(` – Peon Aug 27 '12 at 12:50
  • @timpeterson Then the same pattern works, just don't use all the complicated concatenation to build the array key. Just `$out[$elem['date']]` – Michael Berkowski Aug 27 '12 at 12:55
  • @MichaelBerkowski ok i can't quite get it, look at this gist: https://gist.github.com/3488345. I'd like to keep the $myArray array structure but this code doesn't do it. There should be only 2 sub-arrays in the `$out` array. – tim peterson Aug 27 '12 at 13:21
  • @timpeterson You are missing the part of my algorithm where it first checks if the key in `$out` already exists. If it already exists, add to it, otherwise create it with the current value. You are appending each of $myArray onto $out. – Michael Berkowski Aug 27 '12 at 13:24
  • @MichaelBerkowski, awesome, now i understand! – tim peterson Aug 27 '12 at 14:01
2

Here's how I would do it. The result will be in $newArray with datetime objects as keys. If you just want it as an indexed array it should be pretty easy to do.

// Example array
$myArray = array(
    array(
    'date' => new DateTime('1993-08-11'),
    'value' => 3
    ),

    array(
    'date' => new DateTime('1993-08-11'),
    'value' => 5
    )
);

$newArray = array();

foreach($myArray as $element)
{
    $iterationValue = $element['value'];
    $iterationDate = $element['date'];
    $dateKey = $iterationDate->format('Y-m-d');

    if(array_key_exists($dateKey, $newArray))
    {
        // If we've already added this date to the new array, add the value
        $newArray[$dateKey]['value'] += $iterationValue;
    }
    else
    {
        // Otherwise create a new element with datetimeobject as key
        $newArray[$dateKey]['date'] = $iterationDate;
        $newArray[$dateKey]['value'] = $iterationValue;
    }
}

nl2br(print_r($newArray));

Actually ended up doing the pretty much the same thing as @MichaelBerkowski solution. Still, having DateTime objects is always more flexible when you wan't to do things with the dates later in your application.

Edit: Now tested it and fixed errors

Jonathan
  • 2,968
  • 3
  • 24
  • 36
  • -@MrAzulay, thanks for your solution, is `format()` not declared somewhere? – tim peterson Aug 27 '12 at 14:03
  • 1
    @timpeterson What do you mean exactly? format() is a method in datetime that converts the date to a string. My first intention was to use the object as the key but I forgot that's not possible in PHP. So I had to make it a string – Jonathan Aug 27 '12 at 14:09
  • oh sorry, missed the `new DateTime('xxxx-xx-xx')` declaration, thought it was just `xxxx-xx-xx`. thanks – tim peterson Aug 27 '12 at 15:01