Restructure an array of events in PHP combining attendees with common location

Viewed 62

I have a PHP array that holds the dates, locations and time a person will be attending an event. It starts in this format (The original array is always delivered in date/time order):

array(2) {
  [0]=>
  array(2) {
    ["date"]=>
    string(8) "1st June"
    ["events"]=>
    array(4) {
      [0]=>
      array(3) {
        ["location"]=>
        string(7) "Venue A"
        ["name"]=>
        string(4) "Pete"
        ["time"]=>
        string(6) "4.00pm"
      }
      [1]=>
      array(3) {
        ["location"]=>
        string(7) "Venue A"
        ["name"]=>
        string(4) "John"
        ["time"]=>
        string(6) "4.30pm"
      }
      [2]=>
      array(3) {
        ["location"]=>
        string(7) "Venue B"
        ["name"]=>
        string(5) "Chris"
        ["time"]=>
        string(6) "7.30pm"
      }
      [3]=>
      array(3) {
        ["location"]=>
        string(7) "Venue A"
        ["name"]=>
        string(4) "Mark"
        ["time"]=>
        string(6) "8.00pm"
      }
    }
  }
  [1]=>
  array(2) {
    ["date"]=>
    string(8) "2nd June"
    ["events"]=>
    array(4) {
      [0]=>
      array(3) {
        ["location"]=>
        string(7) "Venue B"
        ["name"]=>
        string(4) "Fred"
        ["time"]=>
        string(6) "5.00pm"
      }
      [1]=>
      array(3) {
        ["location"]=>
        string(7) "Venue C"
        ["name"]=>
        string(5) "Boris"
        ["time"]=>
        string(6) "6.00pm"
      }
      [2]=>
      array(3) {
        ["location"]=>
        string(7) "Venue A"
        ["name"]=>
        string(6) "Rupert"
        ["time"]=>
        string(6) "7.00pm"
      }
      [3]=>
      array(3) {
        ["location"]=>
        string(7) "Venue A"
        ["name"]=>
        string(5) "David"
        ["time"]=>
        string(6) "9.00pm"
      }
    }
  }
}

What I need to do is combine this so attendees at the same location are grouped together where another attendee in a different location doesn't break up the list. So the above array would be changed to this:

array(2) {
  [0]=>
  array(2) {
    ["date"]=>
    string(8) "1st June"
    ["events"]=>
    array(3) {
      [0]=>
      array(2) {
        ["location"]=>
        string(7) "Venue A"
        ["attendees"]=>
        array(2) {
          [0]=>
          array(2) {
            ["name"]=>
            string(4) "Pete"
            ["time"]=>
            string(6) "4.00pm"
          }
          [1]=>
          array(2) {
            ["name"]=>
            string(4) "John"
            ["time"]=>
            string(6) "4.30pm"
          }
        }
      }
      [1]=>
      array(2) {
        ["location"]=>
        string(7) "Venue B"
        ["attendees"]=>
        array(1) {
          [0]=>
          array(2) {
            ["name"]=>
            string(5) "Chris"
            ["time"]=>
            string(6) "7.30pm"
          }
        }
      }
      [2]=>
      array(2) {
        ["location"]=>
        string(7) "Venue A"
        ["attendees"]=>
        array(1) {
          [0]=>
          array(2) {
            ["name"]=>
            string(4) "Mark"
            ["time"]=>
            string(6) "8.00pm"
          }
        }
      }
    }
  }
  [1]=>
  array(2) {
    ["date"]=>
    string(8) "2nd June"
    ["events"]=>
    array(3) {
      [0]=>
      array(2) {
        ["location"]=>
        string(7) "Venue B"
        ["attendees"]=>
        array(1) {
          [0]=>
          array(2) {
            ["name"]=>
            string(4) "Fred"
            ["time"]=>
            string(6) "5.00pm"
          }
        }
      }
      [1]=>
      array(2) {
        ["location"]=>
        string(7) "Venue C"
        ["attendees"]=>
        array(1) {
          [0]=>
          array(2) {
            ["name"]=>
            string(5) "Boris"
            ["time"]=>
            string(6) "6.00pm"
          }
        }
      }
      [2]=>
      array(2) {
        ["location"]=>
        string(7) "Venue A"
        ["attendees"]=>
        array(2) {
          [0]=>
          array(2) {
            ["name"]=>
            string(6) "Rupert"
            ["time"]=>
            string(6) "7.00pm"
          }
          [1]=>
          array(2) {
            ["name"]=>
            string(5) "David"
            ["time"]=>
            string(6) "9.00pm"
          }
        }
      }
    }
  }
}

So here's how I've figured out to do it juggling variables and stepping through the original array:

// $eventsArray holds the original array data a per the first example
$newArray = array();
foreach($eventsArray as $day) {
    $curLocation = '';
    $todaysEvents = array();
    $teKey = -1;
    foreach($day['events'] as $event) {
        if($curLocation==$event['location']) {
            $todaysEvents[$teKey]['attendees'][] = array(
                'name' => $event['name'],
                'time' => $event['time']
            );
        } else {
            $todaysEvents[] = array(
                'location' => $event['location'],
                'attendees' => array(
                    array(
                        'name' => $event['name'],
                        'time' => $event['time']
                    )
                )
            );
            $teKey = $teKey + 1;
        }
        $curLocation=$event['location'];
    }
    $newArray[] = array(
        'date' => $day['date'],
        'events' => $todaysEvents
    );
}

And this works! So why am I asking a question on Stack? Because this feels awfully clunky and I'm not convinced there isn't a much more efficient way of doing it. I get the feeling I'm adding overhead here where it's not necessary.

2 Answers

Here's an alternative version using some PHP 7 (7.3+) features:

$resultEvents = [];

foreach ($events as ['date' => $date, 'events' => $dayEvents]) {
  $resultDayEvents = ['date' => $date, 'events' => []];

  foreach ($dayEvents as ['location' => $location, 'name' => $name, 'time' => $time]) {
    $attendee = ['name' => $name, 'time' => $time];
    if ($location === ($previousLocation ?? null)) {
      $resultDayEvents['events'][array_key_last($resultDayEvents['events'])]['attendees'][] = $attendee;
    } else {
      $resultDayEvents['events'][] = ['location' => $location, 'attendees' => [$attendee]];
    }
    $previousLocation = $location;
  }

  $resultEvents[] = $resultDayEvents;
}

Demo: https://3v4l.org/bC4LK

You can use references like a cursor to keep track of the last event processed. Less effort to compare location and add attendees. But that comes with the complexity of getting rid of it properly.

Also, but a minor thing is to use strict comparisons... Can avoid some frustrating moments by being more predectible...

// $eventsArray holds the original array data a per the first example
$newArray = array();
foreach($eventsArray as $day) {
    $todaysEvents = array();
    $curEvent = null;
    foreach($day['events'] as $event) {
        if($curEvent===null || $curEvent['location']!==$event['location']) {
            unset($curEvent);
            $curEvent = array(
                'location' => $event['location'],
                'attendees' => array()
            );
            $todaysEvents[] = &$curEvent;
        }

        $curEvent['attendees'][] = array(
            'name' => $event['name'],
            'time' => $event['time']
        );
    }
    unset($curEvent);

    $newArray[] = array(
        'date' => $day['date'],
        'events' => $todaysEvents
    );
}
Related