Adding data to a two-dimensional array using foreach

I have a two-dimensional array and I need to add key-value pairs to the ‘inner’ arrays conditionally, and then write the updated arrays back to the outer array. All the ‘inner’ arrays have the same structure. In the snippet below the first or outer ‘foreach’ is looping around calculating $dist values. It’s the inner ‘foreach’ loop that concerns me here.

I am trying to add the key-value pairs ‘dist=$dist’ and ‘unit’=‘miles’ to each $listing. They get added to $listing, but this altered $listing wasn’t getting written back to $listings (plural). So I’ve resorted to writing the altered $listing into a new outer array, and then changing its name back to $listings afterwards. Surely there’s a better way ?


//	Calculate all the distances and add to $listing array
	foreach ($bus_ids as $bus_id) {
		if (array_key_exists($bus_id, $latlongs)) {
			$lat = $latlongs[$bus_id]['lat'];
			$lng = $latlongs[$bus_id]['lng'];
			$dist = distance($lat, $lng, $lat2, $lng2, true);
//			echo $bus_id . " Distance: " . $dist;
			foreach ($listings as $listing) {
				if ($bus_id == $listing['bus_id']) {
					$listing['dist'] = $dist;
					$listing['unit'] = 'miles';
					$newlistings[] = $listing;
					break;  // bus_id is unique
				}
			}
		}
	}
	$listings = $newlistings;

I have tried:

foreach ($listings as $key => $listing) {
         . . .
             $listings[$key] = $listing;
             . . .

and also:

foreach ($listings as &$listing) {

Neither seem to work, but perhaps I’ve not got it quite right.
Curiously when it comes to altering the values in those same key-value pairs the assignment operator seems to work OK. This code follows almost immediately after the snippet above.


//      $listings gets sorted by the value of $listing['dist']

//	Convert small distances to yards
foreach ($listings as &$listing) {
	if (isset($listing['dist']) && $listing['dist'] < 0.5) {
		$listing['dist'] = round($listing['dist']*1760,-1);
		$listing['unit'] = 'yards';
	}
//	$newlistings[] = $listing; (not needed here)
}
//	$listings = $newlistings; (not needed here)

It appears that I can’t ADD to an array using the assignment operator or ‘as $key => $value’ construction, but it is OK for altering something that already exists. Or have I just got my syntax muddled ?

You see that little & before $listing in the foreach of last code block? It’s pretty significant

Now; what EXACTLY are you trying to do? Or, to put it in a phase my programming brain can work on it (it’s only 9 AM), answer the two questions:

What do you have BEFORE (Input)
What do you want AFTER (Output)

Thank you for your response. I’m sorry if I did not give an adequate explanation of what I am (successfully) doing. My question was aimed at finding out if there was a better way (or why what appears to be the right way isn’t working for me).

Right at the start of this process I have an array called $listings (plural). It contains many sub-arrays (about 200) which represent records from a MySQL database. These sub-arrays all have the same structure. If I were to create them manually they’d look like this:


listings[] = ('bus_id' => '1234', 'name' => 'name1', ......);
listings[] = ('bus_id' => '1235', 'name' => 'name2', ......);
and so on

They DO NOT include the KVPs ‘dist’ and ‘unit’. In first of the three ‘foreach’ loops I am finding latlong co-ordinates from another table and calculating a distance, and I’m using the second ‘foreach’ to INSERT NEW KVPs into each sub-array when the appropriate record is found. They then look like this:


listings[] = ('bus_id' => '1234', 'name' => 'name1', 'dist' => $dist1, 'unit' => $unit......);
listings[] = ('bus_id' => '1235', 'name' => 'name2', 'dist' => $dist2, 'unit' => $unit......);
and so on. At this point 'unit' defaults to miles.

Now I need the changed sub-array to be ‘written’ back to $listings. That’s where I thought ‘&$value’ would come in, but it didn’t seem to work, so I resorted to saving the changed sub-arrays to a new ‘wrapper’ array ($newlistings), and then changed its name afterwards. (OK, so the way the PHP Manual describes how ‘&$value’ works is that it allows me to work on the actual $value rather than a copy, but it achieves the same effect as what I’ve called ‘writing back’. How you look at it is less important than what it achieves.)

Next I sort the order of the sub-arrays within the main array by the value of ‘dist’ within each sub-array. That works fine, so no more on that score. EXCEPT to say that this is where it fails when I try to use’ &$value’ above. I get "Undefined index: dist…’ which tells me the added KVPs haven’t been ‘written back’ (as I call it) to the original $listings. So long as I write my augmented sub-arrays to another wrapper ($newlistings in my code) all is well. The presence of ‘&$listings’ in the first code sample in my original posting made no difference, so I should probably have left it our for clarity.

Finally in the third ‘foreach’ I go through the sub-arrays looking for low values of ‘dist’ and converting them from miles to yards, and changing the ‘unit’ to yards too. In this case the ‘&$value’ seems to have the desired effect of allowing me to work on the actual listings rather than a copy, so I don’t explicitly have to write them to another array.


//    Convert small distances to yards
foreach ($listings as &$listing) {
    if (isset($listing['dist']) && $listing['dist'] < 0.5) {
        $listing['dist'] = round($listing['dist']*1760,-1);
        $listing['unit'] = 'yards';
    }
//    $newlistings[] = $listing; (not needed here)
}
//    $listings = $newlistings; (not needed here)
  

As before, my question boils down very simply to: Why does ‘&$value’ appear to work when I’m just altering existing KVPs, but NOT when I’m adding additional ones ?

If you’d like a summary, it’s like this:

Case 1: (&$value does not work)
Before: An array of arrays
After: An array of arrays which (all) contain some extra KVPs

Case2: (&$value works)
Before: An array of arrays
After: An array of arrays which contain the same KVPs, two of which have been altered.

I hope this is understandable. It has become very long.

Well, in developing an answer to this, i have to ask a question.
$dist = distance($lat, $lng, $lat2, $lng2, true);

Where are $lat2 and $lng2 defined?

Thank you for your response. I fear we may get distracted here. The lat and long values are being pulled out of the database and fed into the ‘distance’ function, which returns the distance between the two points. I suggest it doesn’t matter what’s going on there, the point is that apparently ‘&$value’ doesn’t work if KVPs are being added to the sub-arrays. Period.

I know the distance values are good because they run on through the script and print out on the screen.

Well my point was that $lat2 and $lon2 dont get set on each iteration of the loop, so you may be getting eroneous distances, unless all distances are being measured from a single point.

anyway, here’s how i’d do it.


foreach($listings AS &$listing) {
  if (array_key_exists($listing['bus_id'], $latlongs)) {
    $listing['dist'] = distance($latlongs[$listing['bus_id']]['lat'], $latlongs[$listing['bus_id']]['lng'], $lat2, $lng2, true); 
    $listing['unit'] = 'miles';
  }
}

Thank you. All the distances are indeed calculated from a single point (the user’s location, which can be changed elsewhere). Your suggestion shortens the code quite a bit (at the price of making it harder to follow, perhaps). However, it still doesn’t work unless I reintroduce my $newlistings.


	foreach($listings AS &$listing) {
	  if (array_key_exists($listing['bus_id'], $latlongs)) {
	    $listing['dist'] = distance($latlongs[$listing['bus_id']]['lat'], $latlongs[$listing['bus_id']]['lng'], $lat2, $lng2, true);
	    $listing['unit'] = 'miles';
            $newlistings[] = $listing;
	  }
	}
	$listings = $newlistings;

Without it I still get the ‘Undefined Index: dist’ error.

You can see it in rudimentary form at www.mobile.holidaymullandiona.com
Click on one of the location buttons (NOT geo-location, it’s not working yet) and the on ‘Accommodation’/‘Eating’/‘Attractions’/
You should be presented with a list in ascending order of distance from your chosen location. There’s no built-in back button, but if you do go back you can repeat from a different dimension.

I’ve done this in my test enviroment:


<?php
$vals = array("moo","cow");
$arr = array(array(),array());
foreach($arr AS $key => &$myarr) {
  if(array_key_exists($key,$vals)) {
    $myarr['new'] = $vals[$key];
  }
}
print_r($arr);
?>

works absolutely fine. So the system should work… are you using an older version of PHP? (not that that should change anything, really)

Thank you.

Your test version certainly worked, and so I modified it to bring it closer to my script. As you can see, instead of running a calculation within the IF clause (as in my original), I’ve added on two KVPs from the $latlong array.

<?php
$listings = array (
	0 => array("bus_id" => "1234", "name" => "Jim"),
	1 => array("bus_id" => "1235", "name" => "Fred")
	);

$latlongs = array (
	"1234" => array("lat" => 56.31582, "lng" => 6.23551),
	"1235" => array("lat" => 55.58868, "lng" => 5.01604)
	);

foreach($listings AS &$listing) {
	if(array_key_exists($listing['bus_id'],$latlongs)) {
		$listing['lat'] = $latlongs[$listing['bus_id']]['lat'];
		$listing['lng'] = $latlongs[$listing['bus_id']]['lng'];
		$listing['unit'] = 'miles';
	}
}

print_r($listings);
//Prints out:
/* Array (
	[0] => Array ( [bus_id] => 1234 [name] => Jim [lat] => 56.31582 [lng] => 6.23551 [unit] => miles )
    [1] => Array ( [bus_id] => 1235 [name] => Fred [lat] => 55.58868 [lng] => 5.01604 [unit] => miles )
    )
*/
?>

It still works, which leaves me wondering why my original version doesn’t (without introducing the $newlisting array). Perhaps there’s some important aspect I’ve overlooked. I shall continue to look at it, but I’m not going to go grey over a couple of lines of code.
Thank you for your help.

Hi,

As far as I know, foreach will only loop though the elements of the selected array

if you want to display all the dimentions of an array use the function

print_r($array);

if you only want to print out certain parts of an array, you could use this function.

function PrintArray($array)
{

if(is_array($array))
{

foreach($array as $key=>$value)
{

if(is_array($value)
{

// the value of the current array is also a array, so call this function again to process that array

PrintArray($value)

}
else
{

// This part of the array is just a key/value pair

echo “$key: $value<br>”;

}

}

}

}

that will do the same as print_r(); however you can then limit what is printed by placing some conditions where i have the line

echo “$key: $value<br>”;

so you could change that to

if($key == $name)
{
echo “$key: $value<br>”;
}

so now it will only print the array element when key == name

Hope you get help.
Jeffrey


Renewgraphicdesign.com.au

Hello Jeffrey. The issue isn’t about the printing, it’s about using a ‘foreach’ loop to tack additional KVPs onto a two-dimensional array. My last posting probably doesn’t make much sense to someone picking up the thread now.

Hi,

Well, I think this may work for you.

function merge_db_lists ($list1, $list2) {
  $final_array = array();
  $final_array = go_through_list($list1, $final_array);
  $final_array = go_through_list($list2, $final_array);
  return $final_array;
}   

function go_through_list($list,$output){
  foreach (array_keys($list) as $key){
    if (array_key_exists($key, $output)){
      foreach ($list[$key] as $item ){
        $output[$key][] = $item;
      }  
      arsort($output[$key]);
    }
    else{
      $output[$key] = $list[$key];
    }  
  }
  return $output;
}

Thank you.

Thanks, Jeffery. It seems almost more complicated than what I was doing before. Since my original code (writing the augmented arrays into the $newlisting array) works perfectly well I’m going to leave it at that for now. I’m sure i’ll come back to it when I’ve nothing else to do.

Thank you both for your interest and your time.