Most efficient way to empty an array - php

I have an array containing several keys, values, objects etc.. I need to empty that array but I'd like to do it in the most efficient manner.
The best I can come up with is:
foreach ($array as $key => $val) unset($array[$key]);
But I don't like the idea of having to loop through the array to just empty it.. surely there's a nice slick/clever way of doing this without wasting memory creating a new array?
Note: I'm not sure myself if it does cost extra memory in creating the array as new again. If it doesn't then $array = new array(); would be a fine way of 'emptying' it.

Just try with:
$array = array();

It highly depends on what you mean.
To empty the current reference you can always do
$array = array();
To completely remove the current instance from the scope
unset($array);
Unfortunately both of these cases don't necessarily mean the memory associated with each element is released.
PHP works with something called "references" for your variables. Your variables are actually labels or references pointing to data, not the actual container for data.
The PHP garbage collector can offer more insight on this subject.
Now take a look at this example, taken from the docs:
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );# a: (refcount=3, is_ref=0)='new string'
unset( $b, $c );
xdebug_debug_zval( 'a' );# a: (refcount=1, is_ref=0)='new string'
This unfortunately applies to all your variables. Including arrays. Cleaning up the memory associated with the array is a whole different subject I'm afraid.
I've noticed a longer discussion in the comments regarding using unset() on each individual key.
This feels like extremely bad practice. Consider the following code:
class A{
function __construct($name){$this->name=$name;}
function __destruct(){echo $this->name;}
}
$a=array();
$b=array();
$c=array();
for($i=0;$i<5;$i++) {
$a[]=new A('a');
$b[]=new A('b');
$c[]=new A('c');
}
unset($a);
$b=array();
echo PHP_EOL.'done'.PHP_EOL;
This will output:
aaaaabbbbb
done
ccccc
When the reference to a particular data structure reaches 0, it is cleaned from memory.
Both =array() and unset will do the same thing.
Now if you don't actually need array() you can use null :
$array=null;
This keeps the label in memory, but removes the reference it held to any particular data.

It's simple:
$array = array();
$array will be existing and type of array (but empty), and your data can be garbaged later from memory.

Well... why not: $array = array(); ?

As Suresh Kamrushi pointed out, I could use array_keys:
foreach (array_keys($array) as $key) unset($array[$key]);
This is probably the nicest solution for now.. but I'm sure someone will come up with something better soon :)

Try this:
// $array is your original array
$array = array_combine( array_keys( $array ), array_fill( 0, count($array), 0 ) );
The above will blank your array keeping the keys intact.
Hope this helps.

Related

How to create an array from values of another array

This is probably a question with a very simple answer but I am unable to figure it out. How could I write a foreach loop to add elements from an array to another array? Below I have array $A and I want to be able to write code to come up with something like $B from $A.
$A = array('0'=>array('name'=>'Oscar'),'1'=>array('name'=>'Jake'));
$B = array('0'=>'Oscar','1'=>'Jake');
Here is what I came up with, but it's not working.
$B = array();
foreach ($A as $element=>$data) {
$B[] = $element['name'];
}
This should work:
$b = array_map(function($content) {
return $content['name'];
}, $a);
array_map is a method with a callback that loops over an array and creates a new loop, depending on the return value in the function.
I use array_map instead of a foreach loop, because array methods of PHP are optimized and improve performance when used right.
Docs about array_map can be found here.
What I always like to do, is visualize it for myself.
You can use multiple ways to visualize how the variable looks like and go from there. My personal favorites:
debugging using Xdebug
print_r()
var_dump()

When is foreach with a parameter by reference dangerous?

I knew, that it can be dangerous to pass the items by reference in foreach.
In particular, one must not reuse the variable that was passed by reference, because it affects the $array, like in this example:
$array = ['test'];
foreach ($array as &$item){
$item = $item;
}
$item = 'modified';
var_dump($array);
array(1) {
[0]=>
&string(8) "modified"
}
Now this here bite me: the content of the array gets modified inside the function should_not_modify, even though I don't pass the $array by value.
function should_not_modify($array){
foreach($array as &$item){
$item = 'modified';
}
}
$array = ['test'];
foreach ($array as &$item){
$item = (string)$item;
}
should_not_modify($array);
var_dump($array);
array(1) {
[0]=>
&string(8) "modified"
}
I'm tempted to go through my whole codebase and insert unset($item); after each foreach($array => &$item).
But, since this is a big task and introduces a potentially useless line, I would like to know if there is a simple rule to know when foreach($array => &$item) is safe without a unset($item); after it, and when not.
Edit for clarification
I think I understand what happens and why. I also know what is best to do against: foreach($array as &$item){...};unset($item);
I know that this is dangerous after foreach($array as &$item):
reuse the variable $item
pass the array to a function
My question is: Are there other cases that are dangerous, and can we build an exhaustive list of what is dangerous. Or the other way round: is it possible to describe when it is not dangerous.
About foreach
First of all, some (maybe obvious) clarifications about two behaviors of PHP:
foreach($array as $item) will leave the variable $item untouched after the loop. If the variable is a reference, as in foreach($array as &$item), it will "point" to the last element of the array even after the loop.
When a variable is a reference then the assignation, e.g. $item = 'foo'; will change whatever the reference is pointing to, not the variable ($item) itself. This is also true for a subsequent foreach($array2 as $item) which will treat $item as a reference if it has been created as such and therefore will modify whatever the reference is pointing to (the last element of the array used in the previous foreach in this case).
Obviously this is very error prone and that is why you should always unset the reference used in a foreach to ensure following writes do not modify the last element (as in example #10 of the doc for the type array).
About the function that modifies the array
It's worth noting that - as pointed out in a comment by #iainn - the behavior in your example has nothing to do with foreach. The mere existence of a reference to an element of the array will allow this element to be modified. Example:
function should_not_modify($array){
$array[0] = 'modified';
$array[1] = 'modified2';
}
$array = ['test', 'test2'];
$item = & $array[0];
should_not_modify($array);
var_dump($array);
Will output:
array(2) {
[0] =>
string(8) "modified"
[1] =>
string(5) "test2"
}
This is admittedly very suprising but explained in the PHP documentation "What References Do"
Note, however, that references inside arrays are potentially dangerous. Doing a normal (not by reference) assignment with a reference on the right side does not turn the left side into a reference, but references inside arrays are preserved in these normal assignments. This also applies to function calls where the array is passed by value. [...] In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.
With the following example (copy/pasted):
/* Assignment of array variables */
$arr = array(1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* The contents of $arr are changed even though it's not a reference! */
It's important to understand that when creating a reference, for example $a = &$b then both $a and $b are equal. $a is not pointing to $b or vice versa. $a and $b are pointing to the same place.
So when you do $item = & $array[0]; you actually make $array[0] pointing to the same place as $item. Since $item is a global variable, and references inside array are preserved, then modifying $array[0] from anywhere (even from within the function) modifies it globally.
Conclusion
Are there other cases that are dangerous, and can we build an exhaustive list of what is dangerous. Or the other way round: is it possible to describe when it is not dangerous.
I'm going to repeat the quote from the PHP doc again: "references inside arrays are potentially dangerous".
So no, it's not possible to describe when it is not dangerous, because it is never not dangerous. It's too easy to forget that $item has been created as a reference (or that a global reference as been created and not destroyed), and reuse it elsewhere in your code and corrupt the array. This has long been a topic of debate (in this bug for example), and people call it either a bug or a feature...
The accepted answer is the best, but I'd like to give a complement: When is unset($item); not necessary after a foreach($array as &$item) ?
$item: if it is never reused after, it cannot harm.
$array: the last element is a reference. This always dangerous, for all the reasons already stated.
So what does change that element form being a reference to a value ?
the most cited: unlink($item);
when $item falls out of scope when the array is returned from a function, then the array becomes 'normal' after being return from the function.
function test(){
$array = [1];
foreach($array as &$item){
$item = $item;
}
var_dump($array);
return $array;
}
$a = test();
var_dump($a);
array(1) {
[0]=>
&int(1)
}
array(1) {
[0]=>
int(1)
}
But beware: if you do anything else before returning, it can bite !
You can break the reference by "json decode/encode"
function should_not_modify($array){
$array = json_decode(json_encode($array),false);
foreach($array as &$item){
$item = 'modified';
}
}
$array = ['test'];
foreach ($array as &$item){
$item = (string)$item;
}
should_not_modify($array);
var_dump($array);
The question is purely academic, and this is a bit of a hack. But, it's sort of fun, in a stupid programming way.
And of course it outputs:
array(1) {
[0]=>string(4) "test"
}
As a side the same thing works in JavaScript, which also can give you some wonky-ness from references.
I wish I had a good example, because I've had some "weird" stuff happen, I mean like some quantum entanglement stuff. This one time at a PHP camp, I had a recursive function ( pass by reference ) with a foreach ( pass by reference ) and well it sort of ripped a hole in the space time continuum.

Best practice for clearing an array

I have an array that I would like to clear the values out of and I'm wondering what the best way to accomplish this is.
I tried setting it to nothing:
$array = array();
... later on
$array = "";
Afterwards I'll add new values to it later:
foreach($something as $thing):
$array[] = $thing['item'];
endforeach;
And it seems to have done what I needed it too. But after a quick search online I'm seeing a lot of recommendations to do the following instead:
unset($array);
$array = array();
Is there any difference between this action and the one I performed up top?
Setting $array to "" sets your variable to a string value, and unset removes the variable. Since you are just trying to clear the array, then $array = array() should be good enough.
I believe that array() explicitly defines it as an array. Your first statement $array = "" sets it to an empty string. Using unset will "reset" the variable, so it's neither a string or array until you assign a value to it, and $array = array() simply defines it as a new blank array.

Return first key of associative array in PHP

I'm trying to obtain the first key of an associative array, without creating a temporary variable via array_keys() or the like, to pass by reference. Unfortunately both reset() and array_shift() take the array argument by reference, so neither seem to be viable results.
With PHP 5.4 I'll be in heaven; array_keys($array)[0];, but unfortunately this of course is not an option either.
I could create a function to serve the purpose, but I can only imagine there is some concoction of PHP's array_* functions that will produce the desired result in a single statement, that I cannot think of or come up with.
So:
$array = array('foo' => 'bar', 'hello' => 'world');
$firstKey = assorted_functions($array); // $firstKey = 'foo'
The reason for the "no reference" clause in my question is only for the fact that I assume array_keys() will be required (if there is a way passing by reference, please fire away)
I'd use key(), but that requires a reset() as I'm not sure where the pointer will be at the time of this operation.
Addendum
I'm following up on a realization I had recently: as I mentioned in the comments, it'll use the memory all the same, so if that's a concern, this question hath no solution.
$a = range(0,99999);
var_dump(memory_get_peak_usage()); // int(8644416)
$k = array_keys($a)[0];
var_dump(memory_get_peak_usage()); // int(17168824)
I knew this, as PHP doesn't have such optimization capabilities, but figured it warranted explicit mention.
The brevity of the accepted answer is nice though, and'll work if you're working with reasonably sized arrays.
Although array_shift(array_keys($array)); will work, current(array_keys($array)); is faster as it doesn't advance the internal pointer.
Either one will work though.
Update
As #TomcatExodus noted, array_shift(); expects an array passed by reference, so the first example will issue an error. Best to stick with current();
You can use reset and key:
reset( $array );
$first_key = key( $array );
or, you can use a function:
function firstIndex($a) { foreach ($a as $k => $v) return $k; }
$key = firstIndex( $array );
array_shift(array_keys($array))
each() still a temporary required, but potentially a much smaller overhead than using array_keys().
What about using array_slice (in combination with array_keys for associative arrays)?
$a = range(0,999999);
var_dump(memory_get_peak_usage());
$k = array_keys(array_slice($a, 0, 1, TRUE))[0];
var_dump(memory_get_peak_usage());
var_dump($k);
$k = array_keys($a)[0];
var_dump(memory_get_peak_usage());
Gives as output (at least with me):
int(36354360)
int(36355112)
int(0)
int(72006024)
int(0)

Emptying a PHP array

I want to clear all elements from an array object (which can be a standard PHP array, an ArrayObject or basically any other object that implements the basic array interfaces such as Itertable, ArrayAccess, Countable etc.). However, I do not want to reinstate the object, so I somehow have to unset all the individual elements, instead of creating a new object of the same type. Is there an easy way to do this?
foreach ($array as $key => $element) {
unset($array[$key]);
}
This requires both Traversable and ArrayAccess, Countable is not required. Or obviously just a normal array.
I'm not entirely sure why you need to do it this way, but in answer to your question, you should be able to simply use the array_splice function to remove all of the objects from your array;
$my_array = array('A', 'B', 'C');
array_splice($my_array, 0);
I've never used array_splice to remove all objects from an array, but I assume it works in the same manner.
I am having trouble deciphering what the question is really asking for. Replacing the array/iterator with an empty iterator (an EmptyIterator, or another iterator with no values?) might suffice.
$array = new EmptyIterator;
Try
$my_array = array('A', 'B', 'C');
unset($my_array);
What about a simple unset?
unset($array);
In case you remove the last record from array than the next foreach loop can fail (internaly calling $this->next() )
So it helped me to test the validy and break in this case the next loop.
deleteOffset = true;
foreach ($iterator as $key => $value)
{
if ($deleteOffset && $iterator->offsetExists($key) )
{
$iterator->offsetUnset($key);
}
//if remove last record than the foreach ( $this->next() ) fails so
//we have to break in this case the next ->next call
if (!$iterator->valid()) break;
}
The best place PHP Manual
http://php.net/manual/en/function.unset.php

Categories