Question

First of all, its pretty similar to that "Unsetting elements from array within foreach"

However I do not know a neat way around it. Is there any way to mark items in a foreach loop as "used" or "deleted"?

I basically have an array of objects which are alike in some object-properties, but not identical. I want to join the information of similar objects. The idea is as follows

  1. go through the array by picking one object
  2. with each picked object go again trough the array and find similar ones
  3. once found a similar one, join the information and remove the similar object

thats it.

It works some kind of expected but there is a problem (as usual :-). Here it comes:

Although I removed the similar object in the second loop. The first loop is still picking the removed object. That seems to be because foreach loops on some kind of copy of the array.

For discussion/presentation I adopted the code as follows by just looking a strings and join them. Assume we have an Array like this

$test = array( "a", "b", "c", "b", "c" );

The expected result:

array( "a", "bb", "cc" );

Here is the code:

$test = array( "a", "b", "c", "b", "c" );

foreach ($test as $keyA=>$valueA){
  echo "I am at item ".$valueA." [".$keyA."]<br>";

  foreach ($test as $keyB=>$valueB){

    if ($keyA != $keyB){
      // if not comparing to itself
      echo "=> comparing to ".$valueB." [".$keyB."]";

      if (strcmp($valueA,$valueB)==0){
        // is the same string, ... join and remove
        echo "-- joined and removed [".$keyB."]";
        $test[$keyA]=$valueA.$valueB;
        unset($test[$keyB]);      
      }
      echo "<br>";
    }
  }

}

and the actual result

I am at item a [0]
=> comparing to b [1]
=> comparing to c [2]
=> comparing to b [3]
=> comparing to c [4]
I am at item b [1]
=> comparing to a [0]
=> comparing to c [2]
=> comparing to b [3]-- joined and removed [3]
=> comparing to c [4]
I am at item c [2]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to c [4]-- joined and removed [4]
I am at item b [3]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to cc [2]
I am at item c [4]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to cc [2]

Although Item [3] and [4] where removed it is still picking [3] and [4].

Was it helpful?

Solution

Yes, the foreach loop is working on a copy, but when you are calling unset() you are still, in fact, unsetting that element, even though you haven't changed the copy that the foreach loop is iterating through... so even though the actual array has been changed, the foreach loop doesn't know that, so to speak... you could simply add this check to your code, skipping the iteration if that element has been unset:

foreach ($test as $keyA=>$valueA){

    if (!isset($test[$keyA])) continue;     

    echo "I am at item ".$valueA." [".$keyA."]<br>";

    // etc...
}

The output becomes this, which if I understand your question is what you're looking for:

I am at item a [0]
=> comparing to b [1]
=> comparing to c [2]
=> comparing to b [3]
=> comparing to c [4]
I am at item b [1]
=> comparing to a [0]
=> comparing to c [2]
=> comparing to b [3]-- joined and removed [3]
=> comparing to c [4]
I am at item c [2]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to c [4]-- joined and removed [4]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top