If I want to remove the last element of an array, I can use either of these two code:
array_pop($array); (the return value is not used)
unset($array[count($array) -1]);
Is there any performance or semantic difference between them?
If not, which is preferred?
unset is no good if you need to "do" anything with the removed value (unless you have previously assigned it to something elsewhere) as it does not return anything, whereas array_pop will give you the thing that was the last item.
The unset option you have provided may be marginally less performant since you are counting the length of the array and performing some math on it, but I expect the difference, if any, is negligible.
As others have said, the above is true if your array is numerical and contiguous, however if you array is not structured like this, stuff gets complicated
For example:
<?php
$pop = $unset = array(
1 => "a",
3 => "b",
0 => "c"
);
array_pop($pop);
// array looks like this:
// 1 => "a", 3 => "b"
$count = count($unset) - 1;
unset($count);
// array looks like this because there is no item with index "2"
// 1 => "a", 3 => "b", 0 => "c"
array_pop($array) removes the last element of $array.
unset($array[count($array) -1]); removes the element at index count($array) -1. This element is not neccesarily the last element of the array.
Consider $array = array(0 => 'foo', 2 => 'bar', 1 => 'baz'). In this case , $array[1] is the last element. The code
foreach (array(0 => "foo", 2 => "bar", 1 => "baz") as $key => $value)
echo "$key => $value\n";
prints
0 => foo
2 => bar
1 => baz
Moreover, an element at index count($array) -1 might not even exist. There can be gaps in the set of indices and integer indices can be mixed with string indices.
The return values are different. array_pop returns the last element, while unset doesn't return anything.
For simply removing the last element, array_pop would be better because you don't need to execute count($array)-1, and it is cleaner and more readable.
Yes there is.
Firstly, the unset() option will only work for numerical, contiguous arrays. If your array contains elements that are not numerical, or has any gaps in its numerical sequence, then the unset() call will get the incorrect value from count() and will fail.
Secondly, assuming your array is numerical and contiguous, there is still a difference:
array_pop() will also give you back the value of the popped element as a return value. unset() will not do this.
So if you need to keep using the data, use array_pop().
If you don't need to keep the value, then no, it probably doesn't matter too much which one you use, I suspect that array_pop() may be faster (due to not needing to call count()), but I haven't checked, and to be honest, unless you're doing thousands of calls, the difference will be negligible anyway.
Except for the obvious differences in call syntax and return value...
array_pop always pops whatever is last.
Your count - 1 unsets an element by its numeric id, which only works as you expect it to if all elements are continuously numerically indexed.
For what it's worth, using a bit of existing code that gets called a bit over 2000 times in a run, I put in a $whatevers[]=$whatever (a parameter value) at the top and and array_pop($whatevers) at the bottom.
The function calls itself recursively down to about 7 or 8 levels and (of course) I made $whatevers static so the array grew and shrunk.
The result? The difference between this code in and commented out was unmeasurable down to 100ths of a second on a windows 7 laptop. It varied a fair bit because of other things, but over lots of runs the difference in the averages was meaningless.
The performance overhead of array_pop() just isn't worth a second thought and though unset might be theoretically faster, nobody will ever be able to detect the difference.
As others have mentioned - their functionality is the same, bar the return value from array_pop. However, it's also worth mentioning the possible performance issue of the unset method on a large array due to the count() call.
As Oswald mentions, it is also worth noting that unset() will only be working as expected on numeric keys.
Yes there is a difference
array_pop() will also return removed element eg: last element, and
unset() will not return any thing
I would prefer unset() but you call count() which can consume performance.
An alternative choice is array_slice(array, offset, length, preserve_keys) :
$array = array_slice($array, 0, -1, true);
Another consideration to take into account is that if after deleting the last item you push a new element, you get different results in which the index the new element is placed at:
unset
php > $a = [1,2,3];
php > var_dump($a);
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
php > unset($a[2]);
php > var_dump($a);
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
php > $a[] = 5;
php > var_dump($a);
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[3]=>
int(5)
}
As you see, the new element is placed at index 3 instead of 2.
array_pop
php > $a = [1,2,3];
php > var_dump($a);
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
php > array_pop($a);
php > var_dump($a);
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
php > $a[] = 5;
php > var_dump($a);
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(5)
}
Now the new element is placed at index 2. Maybe this is the most desirable behaviour.
Related
I currently have a column in mysql database that stores a string with delimiters so I can convert it into an array in php.
An example string looks like this:
23,1,1|72,2,0|16,3,1|...etc.
It essentially divides it into 3 major groups with |, and 3 smaller ones with , (if there's a cleaner way, let me know):
1st number is an ID number for an article from a different table
2nd is just a number for indenting purposes
3rd is for visible or not (0 or 1).
I will have an admin section where we'll be able to re-order the major groups (i.e., move group 3 to position 2) and modify specific numbers from the sub-groups (e.g. change 72,1,0 to 72,2,0) I'm not sure how I can accomplish this.
How do I loop through these modifications while keeping the order (or new order) when reinserting into the database?
I was thinking of adding a another number to my string that would determine the position of each major group? Something like this:
1[23,1,1]2[72,2,0]3[16,3,1]
But how do I loop through this and move things around?
Any help would be greatly appreciated.
I agree with the comments about normalization, but if you insist on doing it this way, or are stuck with an existing schema you cannot alter, use the PHP serialize/unserialize functions if you can, rather than string parsing. This will at least allow you to retrieve the data into PHP and modify the array and then save it back.
http://php.net/manual/en/function.serialize.php
I'm joining all comments about the approach used to store data, but nevertheless.
This what can help you to move forward:
/** #var string $s Assumed packet */
$s = "23,3,1|72,1,0|16,2,1"; // Indent is not ordered
/** #var array $parsed Parsed packet */
$parsed = array_map(function ($segment) {
list($artId, $indent, $visibility) = explode(',', $segment);
return [
'ArticleId' => (int)$artId,
'Indent' => (int)$indent,
'Visible' => (int)$visibility !== 0
];
}, explode('|', $s));
usort($parsed, function (array $a, array $b) {
return ($a['Indent'] < $b['Indent']) ? -1 : 1;
});
You'll get following $parsed structure sorted by Indent key:
array(3) {
[0] => array(3) {
["ArticleId"]=> int(23)
["Indent"]=> int(1)
["Visible"]=> bool(true)
}
[1] => array(3) {
["ArticleId"]=> int(72)
["Indent"]=> int(2)
["Visible"]=> bool(false)
}
[2] => array(3) {
["ArticleId"]=> int(16)
["Indent"]=> int(3)
["Visible"]=> bool(true)
}
}
Thus you can alter Indent as you want just applying usort() before/after parsing.
Regarding storing this structure in database the way you decided, you can use JSON format (json_encode(),json_decode()). Such "serialization" way faster than proposed serialize() method and way more faster than $parsed approach + more readable. If you worry about redundancy you can json_encode() array without array keys and add them on parsing or use directly [0], [1], [2] knowing the correspondence beforehand.
If you use json_*() functions you can omit structure parsing bec. it will be decoded right the same you've encoded it for saving. Order can be defined on save using same usort(). This can be considered as improvement by reducing excessive sorts bec. readings/decodings will occur more frequently than saves.
I was interested with your question. So i create this. It may not like what you desired.
<?php
$string = "23,1,1|72,2,0|16,3,1|";
$explode = explode("|", $string);
$a = array();
for($x = 0;$x<count($explode)-1;$x++)
{
$a[] = $explode[$x];
$b[] =explode(',',substr($explode[$x], strpos($explode[$x], ",") + 1));
}
for($y=0;$y<count($b);$y++){
echo $b[$y][0]. '=>'. $a[$y] . '<br>';
}
?>
I am having an issue with understanding why my array containing 3 elements must be sliced into 2 parts each. I wish to access a number I'm pushing into the array only however it seems to print out the index rather than the 'key' value I pushed into it ($number).
I have a 2d array I'm pushing an ID and an integer into, and then sort it :
$array = [[]];
array_push($array, $doc[_id], $number);
array_multisort($array);
I then filter any empty elements:
$array = array_filter($array); //remove null elements
This all works as id expect however the array looks like this by this point:
unrated.array(5)
{
[2]=> object(MongoId)#32 (1)
{ ["$id"]=> string(24) "57b99696ce2350100b000029" }
[3]=> object(MongoId)#31 (1)
{ ["$id"]=> string(24) "57b998ccce2350181700002b" }
[4]=> object(MongoId)#33 (1)
{ ["$id"]=> string(24) "57b99a84ce2350100b00002b" }
[5]=> int(2) [6]=> int(3)
}
Again, this is fine however it means when I loop over the array using the code below it appears to be longer than 3 elements, as I have to slice from 0-6 instead of 0-3:
$array = array_slice($array, 0, 6, true); //only get 3 elements from array
foreach ($array as $key => $value) {
echo $key; //prints out values from 1-5 weirdly.... should just print the $number value
$id = $value->{'$id'};
}
What I am trying to achieve is to find the element in the array with the lowest possible value that was pushed earlier (array_push($array, $doc[_id], $number);) however because I cannot understand why the array is split into 6 rather than 3 parts its even more confusing.
Question in short : How do I access the $number pushed into the array and why is my array 6 seemingly 6 in size when it contains only 3 elements.
Any help would be appreciated, thanks.
To be clear, array_push simply pushes one or more values onto the end of an array. The first argument of array_push is the array you wish to push the value(s) to, and any subsequent argument is a list of values you wish to push. So what you're doing with array_push($array, $doc[_id], $number) is pushing two values ($doc[_id] and $number) to the end of the array $array. array_push will just use the next available index as the key when it adds those values to the array. It will not allow you to specify a key. This is the same thing as doing $array[] = $value.
To specify a key you must assign a value directly to the array key like so: $array[$key] = $value.
Sorry, Im sure this answer is somewhere on the internet (maybe even stackoverflow) but I have spent the last hour searching and cant seem to find the answer...
Does PHP's extract() function add variables to the symbol table using copy-on-write?
I know that PHP function calls all have a copy-on-write by default (unless you specify by reference), just curious about this as I am integrating a templating system so I grab a whole bunch of variables into a $data array, which can be quite large sometimes and am wondering if extracting them before including the template file is the best way to go about this.
thanks!
EDIT:
To clarify:
$array = array('a' => array(1,2,3), 'b' => array(3,4,5), 'c' => array(6,7,8));
extract($array);
//is $a, $b, $c Copy-On-Write at this point? Would be a lot better performance as opposed to allocating 3 new array()'s
//I would like to avoid having this change the original $array values so using EXTR_REFS is not a good solution for me here if I can avoid it and still keep performance!
$a = array(3);
Both versions are possible (extract values and extract by reference) - and it's quite easy to showcase both:
function value() {
$array = array('a' => 1, 'b' => 2);
extract($array);
var_dump($a);
var_dump($b);
$a = 3;
$b = 4;
var_dump($a);
var_dump($b);
var_dump($array);
}
function reference() {
$array = array('a' => 1, 'b' => 2);
extract($array, EXTR_REFS);
var_dump($a);
var_dump($b);
$a = 3;
$b = 4;
var_dump($a);
var_dump($b);
var_dump($array);
}
value();
/*
int(1)
int(2)
int(3)
int(4)
array(2) {
["a"]=>
int(1) // original array item is unchanged
["b"]=>
int(2) // original array item is unchanged
}
*/
reference();
/*
int(1)
int(2)
int(3)
int(4)
array(2) {
["a"]=>
&int(3) // original array item is changed because it's a reference to the extracted variable
["b"]=>
&int(4) // original array item is changed because it's a reference to the extracted variable
}
extract() allows for a second parameter which features EXTR_REFS as an option:
Extracts variables as references. This effectively means that the
values of the imported variables are still referencing the values of
the var_array parameter. You can use this flag on its own or combine
it with any other flag by OR'ing the extract_type.
I would say it does, since there is a EXTR_REFS flag that you can pass that extracts the variables as references. If COW wasn't being used there would be no point to this.
Knowing this, I would say go ahead with the extract if you need to, since the variables you are not modifying are optimised for.
This question already has answers here:
How to check if PHP array is associative or sequential?
(60 answers)
Closed last year.
I'd like to be able to pass an array to a function and have the function behave differently depending on whether it's a "list" style array or a "hash" style array. E.g.:
myfunc(array("One", "Two", "Three")); // works
myfunc(array(1=>"One", 2=>"Two", 3=>"Three")); also works, but understands it's a hash
Might output something like:
One, Two, Three
1=One, 2=Two, 3=Three
ie: the function does something differently when it "detects" it's being passed a hash rather than an array. Can you tell I'm coming from a Perl background where %hashes are different references from #arrays?
I believe my example is significant because we can't just test to see whether the key is numeric, because you could very well be using numeric keys in your hash.
I'm specifically looking to avoid having to use the messier construct of myfunc(array(array(1=>"One"), array(2=>"Two"), array(3=>"Three")))
Pulled right out of the kohana framework.
public static function is_assoc(array $array)
{
// Keys of the array
$keys = array_keys($array);
// If the array keys of the keys match the keys, then the array must
// not be associative (e.g. the keys array looked like {0:0, 1:1...}).
return array_keys($keys) !== $keys;
}
This benchmark gives 3 methods.
Here's a summary, sorted from fastest to slowest. For more informations, read the complete benchmark here.
1. Using array_values()
function($array) {
return (array_values($array) !== $array);
}
2. Using array_keys()
function($array){
$array = array_keys($array); return ($array !== array_keys($array));
}
3. Using array_filter()
function($array){
return count(array_filter(array_keys($array), 'is_string')) > 0;
}
PHP treats all arrays as hashes, technically, so there is not an exact way to do this. Your best bet would be the following I believe:
if (array_keys($array) === range(0, count($array) - 1)) {
//it is a hash
}
No, PHP does not differentiate arrays where the keys are numeric strings from the arrays where the keys are integers in cases like the following:
$a = array("0"=>'a', "1"=>'b', "2"=>'c');
$b = array(0=>'a', 1=>'b', 2=>'c');
var_dump(array_keys($a), array_keys($b));
It outputs:
array(3) {
[0]=> int(0) [1]=> int(1) [2]=> int(2)
}
array(3) {
[0]=> int(0) [1]=> int(1) [2]=> int(2)
}
(above formatted for readability)
My solution is to get keys of an array like below and check that if the key is not integer:
private function is_hash($array) {
foreach($array as $key => $value) {
return ! is_int($key);
}
return false;
}
It is wrong to get array_keys of a hash array like below:
array_keys(array(
"abc" => "gfb",
"bdc" => "dbc"
)
);
will output:
array(
0 => "abc",
1 => "bdc"
)
So, it is not a good idea to compare it with a range of numbers as mentioned in top rated answer. It will always say that it is a hash array if you try to compare keys with a range.
Being a little frustrated, trying to write a function to address all combinations, an idea clicked in my mind: parse json_encode result.
When a json string contains a curly brace, then it must contain an object!
Of course, after reading the solutions here, mine is a bit funny...
Anyway, I want to share it with the community, just to present an attempt to solve the problem from another prospective (more "visual").
function isAssociative(array $arr): bool
{
// consider empty, and [0, 1, 2, ...] sequential
if(empty($arr) || array_is_list($arr)) {
return false;
}
// first scenario:
// [ 1 => [*any*] ]
// [ 'a' => [*any*] ]
foreach ($arr as $key => $value) {
if(is_array($value)) {
return true;
}
}
// second scenario: read the json string
$jsonNest = json_encode($arr, JSON_THROW_ON_ERROR);
return str_contains($jsonNest, '{'); // {} assoc, [] sequential
}
NOTES
php#8.1 is required, check out the gist on github containing the unit test of this method + Polyfills (php>=7.3).
I've tested also Hussard's posted solutions, A & B are passing all tests, C fails to recognize: {"1":0,"2":1}.
BENCHMARKS
Here json parsing is ~200 ms behind B, but still 1.7 seconds faster than solution C!
What do you think about this version? Improvements are welcome!
This question already has answers here:
A numeric string as array key in PHP
(11 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
I've come across an old app that uses an id to name type array, for example...
array(1) {
[280]=>
string(3) "abc"
}
Now I need to reorder these, and a var_dump() would make it appear that that isn't going to happen while the keys are integers.
If I add an a to every index, var_dump() will show double quotes around the key, my guess to show it is now a string...
array(1) {
["280a"]=>
string(3) "abc"
}
This would let me easily reorder them, without having to touch more code.
This does not work.
$newArray = array();
foreach($array as $key => $value) {
$newArray[(string) $key] = $value;
}
A var_dump() still shows them as integer array indexes.
Is there a way to force the keys to be strings, so I can reorder them without ruining the array?
YOU CAN'T!!
Strings containing valid integers will be cast to the integer type. E.g. the key "8" will actually be stored under 8. On the other hand "08" will not be cast, as it isn't a valid decimal integer.
Edit:
ACTUALLY YOU CAN!!
Cast sequential array to associative array
$obj = new stdClass;
foreach($array as $key => $value){
$obj->{$key} = $value;
}
$array = (array) $obj;
In most cases, the following quote is true:
Strings containing valid integers will be cast to the integer type. E.g. the key "8" will actually be stored under 8. On the other hand "08" will not be cast, as it isn't a valid decimal integer.
This examples from the PHP Docs
<?php
$array = array(
1 => "a",
"1" => "b",
1.5 => "c",
true => "d",
);
var_dump($array);
?>
The above example will output:
array(1) {
[1]=> string(1) "d"
}
So even if you were to create an array with numbered keys they would just get casted back to integers.
Unfortunately for me I was not aware of this until recently but I thought I would share my failed attempts.
Failed attempts
$arr = array_change_key_case($arr); // worth a try.
Returns an array with all keys from array lowercased or uppercased. Numbered indices are left as is.
My next attempts was to create a new array by array_combineing the old values the new (string)keys.
I tried several ways of making the $keys array contain numeric values of type string.
range("A", "Z" ) works for the alphabet so I though I would try it with a numeric string.
$keys = range("0", (string) count($arr) ); // integers
This resulted in an array full of keys but were all of int type.
Here's a couple of successful attempts of creating an array with the values of type string.
$keys = explode(',', implode(",", array_keys($arr))); // values strings
$keys = array_map('strval', array_keys($arr)); // values strings
Now just to combine the two.
$arr = array_combine( $keys, $arr);
This is when I discovered numeric strings are casted to integers.
$arr = array_combine( $keys, $arr); // int strings
//assert($arr === array_values($arr)) // true.
The only way to change the keys to strings and maintain their literal values would be to prefix the key with a suffix it with a decimal point "00","01","02" or "0.","1.","2.".
You can achieve this like so.
$keys = explode(',', implode(".,", array_keys($arr)) . '.'); // added decimal point
$arr = array_combine($keys, $arr);
Of course this is less than ideal as you will need to target array elements like this.
$arr["280."]
I've created a little function which will target the correct array element even if you only enter the integer and not the new string.
function array_value($array, $key){
if(array_key_exists($key, $array)){
return $array[ $key ];
}
if(is_numeric($key) && array_key_exists('.' . $key, $array)){
return $array[ '.' . $key ];
}
return null;
}
Usage
echo array_value($array, "208"); // "abc"
Edit:
ACTUALLY YOU CAN!!
Cast sequential array to associative array
All that for nothing
You can append the null character "\0" to the end of the array key. This makes it so PHP can't interpret the string as an integer. All of the array functions (like array_merge()) work on it. Also not even var_dump() will show anything extra after the string of integers.
Example:
$numbers1 = array();
$numbers2 = array();
$numbers = array();
$pool1 = array(111, 222, 333, 444);
$pool2 = array(555, 666, 777, 888);
foreach($pool1 as $p1)
{
$numbers1[$p1 . "\0"] = $p1;
}
foreach($pool2 as $p2)
{
$numbers2[$p2 . "\0"] = $p2;
}
$numbers = array_merge($numbers1, $numbers2);
var_dump($numbers);
The resulting output will be:
array(8) {
["111"] => string(3) "111"
["222"] => string(3) "222"
["333"] => string(3) "333"
["444"] => string(3) "444"
["555"] => string(3) "555"
["666"] => string(3) "666"
["777"] => string(3) "777"
["888"] => string(3) "888"
}
Without the . "\0" part the resulting array would be:
array(8) {
[0] => string(3) "111"
[1] => string(3) "222"
[2] => string(3) "333"
[3] => string(3) "444"
[4] => string(3) "555"
[5] => string(3) "666"
[6] => string(3) "777"
[7] => string(3) "888"
}
Also ksort() will also ignore the null character meaning $numbers[111] and $numbers["111\0"] will both have the same weight in the sorting algorithm.
The only downside to this method is that to access, for example $numbers["444"], you would actually have to access it via $numbers["444\0"] and since not even var_dump() will show you there's a null character at the end, there's no clue as to why you get "Undefined offset". So only use this hack if iterating via a foreach() or whoever ends up maintaining your code will hate you.
Use an object instead of an array $object = (object)$array;
EDIT:
I assumed that if they are integers, I
can't reorder them without changing
the key (which is significant in this
example). However, if they were
strings, I can reorder them how they
like as the index shouldn't be
interpreted to have any special
meaning. Anyway, see my question
update for how I did it (I went down a
different route).
Actually they dont have to be in numeric order...
array(208=>'a', 0=> 'b', 99=>'c');
Is perfectly valid if youre assigning them manually. Though i agree the integer keys might be misinterpreted as having a sequential meaning by someone although you would think if they were in a non-numeric order it would be evident they werent. That said i think since you had the leeway to change the code as you updated that is the better approach.
Probably not the most efficient way but easy as pie:
$keys = array_keys($data);
$values = array_values($data);
$stringKeys = array_map('strval', $keys);
$data = array_combine($stringKeys, $values);
//sort your data
I was able to get this to work by adding '.0' onto the end of each key, as such:
$options = [];
for ($i = 1; $i <= 4; $i++) {
$options[$i.'.0'] = $i;
}
Will return:
array("1.0" => 1, "2.0" => 2, "3.0" => 3, "4.0" => 4)
It may not be completely optimal but it does allow you to sort the array and extract (an equivalent of) the original key without having to truncate anything.
Edit:
This should work
foreach($array as $key => $value) {
$newkey = sprintf('%s',$key);
$newArray["'$newkey'"] = $value;
}
Hi we can make the index of the array a string using the following way. If we convert an array to xml then indexes like [0] may create issue so convert to string like [sample_0]
$newArray = array();
foreach($array as $key => $value) {
$newArray["sample_".$key] = $value;
}
All other answers thus far are hacks that either use fragile workarounds that could break between major PHP versions, create unnecessary gotchas by deliberately corrupting keys, or just slow down your code for no benefit. The various functions to sort arrays yet maintain the crucial key associations have existed since PHP 4.
It is pointless stop PHP from using integer keys, it only does so when the integer representation is exactly the same as the string, thus casting an integer key back to string when reading from the array is guaranteed to return the original data. PHP's internal representation of your data is completely irrelevant as long as you avoid the functions that rewrite integer keys. The docs clearly state which array functions will do that.
An example of sorting, without any hacks, that demonstrates how data remains uncorrupted:
<?php
# use string keys to define as populating from a db, etc. would,
# even though PHP will convert the keys to integers
$in = array(
'347' => 'ghi',
'176' => 'def',
'280' => 'abc',
);
# sort by key
ksort($in);
echo "K:\n";
$i = 1;
foreach ($in as $k => $v) {
echo $i++, "\n";
$k = (string) $k; # convert back to original
var_dump($k, $v);
}
# sort by value
asort($in, SORT_STRING);
echo "\nV:\n";
$i = 1;
foreach ($in as $k => $v) {
echo $i++, "\n";
$k = (string) $k;
var_dump($k, $v);
}
# unnecessary to cast as object unless keys could be sequential, gapless, and start with 0
if (function_exists('json_encode')) {
echo "\nJSON:\n", json_encode($in);
}
The output it produces hasn't changed since v5.2 (with only the JSON missing prior to that):
K:
1
string(3) "176"
string(3) "def"
2
string(3) "280"
string(3) "abc"
3
string(3) "347"
string(3) "ghi"
V:
1
string(3) "280"
string(3) "abc"
2
string(3) "176"
string(3) "def"
3
string(3) "347"
string(3) "ghi"
JSON:
{"280":"abc","176":"def","347":"ghi"}