Is there a faster way to get a value from a JSON array than foreach?

Viewed 1156

I have a JSON array like this:

[
  {"location":"1","distance":"25.75206"},
  {"location":"2","distance":"21.49343"},
  {"location":"3","distance":"24.13432"}
]

Right now I am doing a foreach with every $location to get the corresponding data.

$locations = json_decode($locations, true);

foreach ($locations as $key => $value) {
  if ($value['location'] == $location) {
    $distance = $value['distance']; 
  }
}

The problem is, the arrays can be very big with several thousand items, so doing a foreach is quite resource intensive and can slow things down.

Is there any more "direct" and less resource intensive way of getting each corresponding value?

6 Answers

foreach is quite resourced intensive and can slow things down because it copies input array and works with this copy. If you want to avoid this behavior you can use access by a pointer. For example, you can use reset, next and current construction for working with a pointer

$value = reset($locations);
while ($element !== false)  {
    if ($value['location'] == $location) {
        $distance = $value['distance']; 
    }
    next($locations); 
    $value = current($locations);
}

And of course, you can use a pointer in foreach. For example:

foreach ($locations as $key => &$value) {
  if ($value['location'] == $location) {
    $distance = $value['distance']; 
  }
}

I am not a PHP guy, I don't know if this applies here, but in general (not necessarily in all cases) if you have a list of something and know the size or can detect the end you can iterate through it and access each item with the index number (ie, list[0], list[1], list[2], etc). Iterating through a list in this way is typically faster, but again I don't know PHP and perhaps that isn't true in this situation.

I'll also add, while I don't know the environment in which you are implementing this, in many cases it isn't really going to matter. foreach implementations in many languages are often very optimized.

To me it looks like you have a known location and you're trying to get the information associated with that particular location. If you have the option through whatever API you're using (including if that API is your own code) I would try to leverage said API/Database to query for that specific location rather than getting everything and then looping though it.

If you have no choice but to get the JSON as a large array, then you're kind of out of luck. One thing you might want to do to speed up subsequent "queries" is to create maps of which index corresponds to which location, or if you're only querying by one value (location) to just reindex the array.

$locations = json_decode($locations, true);
$locationsIndexedByLocation = [];
$distanceLocationMap = [];

foreach ($locations as $key => $value) {
  $locationsIndexedByLocation = [$value['location'] => $value];
  $distanceLocationMap = [$value['distance'] => $value['location']];
  if ($value['location'] == $location) {
    $distance = $value['distance']; 
  }
}

Be careful creating large maps though. You can fill up your memory doing this. It should be noted that this is very similar to what a relational database does when you "index" a column. It may be worth it to cache these location objects in a queryable database. If these are coming out of your database, you should leverage said database rather than trying to get everything and filter values in the code.

Also, it looks like you're only ever pulling one $distance value out of this loop. In which case, you could speed it up if you get a match early on in the loop by breaking the loop.

$locations = json_decode($locations, true);

foreach ($locations as $key => $value) {
  if ($value['location'] == $location) {
    $distance = $value['distance'];
    break;
  }
}

Your current format means you have to search for the data. If you changed it to have the location as the attribute name, then you would be able to access it directly...

$locations = '{
"1":"25.75206",
"2":"21.49343",
"3":"24.13432"
}';
$locations = json_decode($locations, true);
echo $locations[3];

Or if you want to keep the current data (which is more verbose)...

$locations = '{
"1":{"location":"1","distance":"25.75206"},
"2":{"location":"2","distance":"21.49343"},
"3":{"location":"3","distance":"24.13432"}}
';
$locations = json_decode($locations, true);
echo $locations[3]['distance'];

If JSON structure is same as what shown in OP, you can use regex to finding target values from JSON instead of parsing it using json_decode and iterate result array.

The regex pattern match JSON object has location key equal to $location.

if (preg_match("/\{\s*\"location\"\s*:\s*\"{$location}\".*?}/", $json, $match)){
    $locations = json_decode("[{$match[0]}]", true)[0];
    $distance = $locations['distance'];
}

Check performance of regex toward json_decode and loop in demo

Just extract the distance values and index on the location values, then access the location index:

$location = 2;
echo array_column($locations, 'distance', 'location')[$location];
Related