0

I have some json data which I've decoded into an assoc array. This array contains a set of names, their respective ids, some relevant data and year. Here is a sample of my data:

Name ID Year
Gary 1 2016
Miller 2 2018
Spike 3 2019
Miller 2 2020
Gary 1 2018
Miller 2 2019
Gary 1 2017
Spike 3 2020

I have sorted this in descending order of year, but I would also like to retrieve all 3 IDs by their latest entry only. Below is my expected output:

Name ID Year
Miller 2 2020
Spike 3 2020
Gary 1 2018

I am not clear on how to get the latest entry from each respective ID.

BzeQ
  • 93
  • 1
  • 11
  • 1
    Does this answer your question? [PHP sort array by two field values](https://stackoverflow.com/questions/4582649/php-sort-array-by-two-field-values) – fonini Aug 03 '22 at 20:18
  • @fonini I'll give that solution a try and see – BzeQ Aug 03 '22 at 20:31

1 Answers1

3

One of the easier ways to do this is to loop through the sorted array and extract the record with the first instance of each ID into another array, keyed by the ID so we know when we have already found that ID.

<?php

$data = [
    ['Name' => 'Gary',      'ID' => 1, 'Year' => 2016],
    ['Name' => 'Miller',    'ID' => 2, 'Year' => 2018],
    ['Name' => 'Spike',     'ID' => 3, 'Year' => 2019],
    ['Name' => 'Miller',    'ID' => 2, 'Year' => 2020],
    ['Name' => 'Gary',      'ID' => 1, 'Year' => 2018],
    ['Name' => 'Miller',    'ID' => 2, 'Year' => 2019],
    ['Name' => 'Gary',      'ID' => 1, 'Year' => 2017],
    ['Name' => 'Spike ',    'ID' => 3, 'Year' => 2020]
];

// Sort all records by year in descending order
usort($data, function ($a, $b)
{
    return $b['Year'] <=> $a['Year'];
});

// Create a temporary array for our unique entries. We will use the IDs as the keys
$latestEntryBuffer = [];

// Loop through the sorted data
foreach ($data as $currRow)
{
    /*
        Set a var for the ID, less typing, less chance for error,
        our IDE will let us know if we mistype it somewhere
    */
    $currID = $currRow['ID'];
    
    /*
        If this ID does not have a record in the buffer yet, set
        the current record in the buffer. Since we sorted all data
        by year descending, the first instance of the ID we encounter
        will be the most recent
     */
    if (!array_key_exists($currID, $latestEntryBuffer))
    {
        $latestEntryBuffer[$currID] = $currRow;
    }
}

// Put all of the rows from the buffer into a simple array
$lastestEntries = array_values($latestEntryBuffer);

print_r($lastestEntries);
echo PHP_EOL;

Output:

    Array
    (
        [0] => Array
            (
                [Name] => Miller
                [ID] => 2
                [Year] => 2020
            )

        [1] => Array
            (
                [Name] => Spike
                [ID] => 3
                [Year] => 2020
            )

        [2] => Array
            (
                [Name] => Gary
                [ID] => 1
                [Year] => 2018
            )

    )

Alternatively you could sort by year ascending and blindly assign each record to the buffer by ID, the last record for each ID would be the most recent.

Rob Ruchte
  • 3,569
  • 1
  • 16
  • 18
  • Isn't the easiest way using `array_slice`? It can extract the last three item with: `array_slice($data, -3);`. – Pedro Amaral Couto Aug 03 '22 at 23:05
  • 1
    Are you always only going to have three? Are you always going to know the number that you need to retrieve? – Rob Ruchte Aug 03 '22 at 23:08
  • @RobRuchte is correct, I will not always know the number of items to retrieve, so using array slice will not be effective. – BzeQ Aug 04 '22 at 04:41
  • 1
    I had a similar idea where I collect all similar items based on ID, sort them, take the latest entry from that and input it into another array, but this answer seems to be less messy than my solution – BzeQ Aug 04 '22 at 04:46
  • @BzeQ, you can solve your problem like that: `array_slice(array_unique(array_map(fn($item) => $item['ID'], $data)), -$number);` – Pedro Amaral Couto Aug 04 '22 at 12:54
  • 1
    Or even array_slice(array_unique(array_column($data, 'ID')), -$number) if all you want is the ID – Rob Ruchte Aug 04 '22 at 13:07
  • I forget about `array_column`. Nevertheless, it's not required to create a buffer, transverse the array, use `array_values`, copy values, use `array_key_exists`, etc. The functional approach takes fewer lines of code, and it's more performant. – Pedro Amaral Couto Aug 04 '22 at 13:21
  • If I misunderstood, and it was supposed to get all the IDs without repetition, this is enough: `array_unique(array_column($data, 'ID'));` – Pedro Amaral Couto Aug 04 '22 at 13:23
  • But all that yields is the ID, he wants the entire entry. Also, fewer lines of code is not the goal. Can someone else easily tell what the code does three years from now? Code is as much communication to other developers (or you yourself in the future) as it is instructions to a compiler. 3 nested inline functions is horrible practice if it's implementing important logic. Will the difference in performance matter given the number of records Probably not. – Rob Ruchte Aug 04 '22 at 13:27
  • @RobRuchte, why do you think this is harder to understand? `array_unique(array_column($data, 'ID'));` I can't put each function call here in a different line. But you can also do this, if you want: `$ids = array_column($data, 'ID'); $ids = array_unique($ids);` I don't understand why that is harder to understand than a buffer, a `foreach`, a `array_key_exists`, `array_values`, etc. – Pedro Amaral Couto Aug 04 '22 at 13:30