How can I access an array dimension, based on another array? - php

Using the following code:
<?php
$array = array(
array(
array('a','b'),
array('c')
)
);
$location = array('0','1');
$text = 'd';
?>
How can I achieve the following, if the number of elements in $location[] is unknown?
$array[$location[0]][$location[1]][] = $text;
In other words, how do I go one dimension into $array for each element in $location[] and add $text there.

That can be achieved with:
$array = array(
array(
array('a','b'),
array('c')
)
);
$location = array('0','1');
$text = 'd';
$pointer = &$array;
foreach($location as $index)
{
$pointer = &$pointer[$index];
}
$pointer[] = $text;
unset($pointer);//this is for safety
Last unset is needed - because if you'll forget to do that, you could end with debugging "mysterious" bugs when you'll later use $pointer somewhere in code (that will obviously have momentary side-effect, change your source data and cause bugs, while whole logic will be ok)

To read:
$value = $array;
foreach ($location as $index) {
$value = $value[$index];
}
To write:
$value =& $array;
foreach ($location as $index) {
if (!array_key_exists($value, $index)) {
$value[$index] = array();
}
$value =& $value[$index];
}
$value[] = $text;

As the other answers show, this can be achieved with references. However, references are a tad tricky, and can prove difficult to debug. When performing the same loop twice with different arrays, or when nesting loops, the results can be rather baffling, not to mention that assigning by reference can in some cases also issue an E_DEPRECATED notice (not in this particular example, but you never know how your code will be used further down the line). I'd suggest using a recursive function, for that reason:
function getLocation(array $in, array $location)
{
$in = $in[$location[0]];
if (is_array($in) && count($location) > 1)
{//more than 1 index left
$location = array_slice($location,1);//or use unshift
return getLocation($in, $location);
}
return $in;
}
That would be my suggestion: It's easy to read, understand and maintain
To add something to a given location, you can alter this function to recursively (re-)assign values:
function addToLocation(array $in, array $location, $value)
{
if (count($location) === 1)
{//optionally add is_array checks
$in[$location[0]][] = $value;
return $in;
}
$in[$location[0]] = addToLocation(
$in[$location[0]],
array_slice($location, 1),
$value
);
return $in;
}
The up-sides:
Easier to debug, use and maintain
requires no greater skill set to use (the function signature says it all, really)
safer (no references to tidy up, which is easily forgotten)
recursion is fun :)
The down-sides/tradeoffs:
recursion gives some extra overhead (larger call-stack, local scope stays in memory etc...)
References are faster than accessing arrays by index (which requires a HashTable lookup, though these are O(1) operations, a reference gives you direct access, no lookup required)
As often: defensive code is slightly slower, and more resource intensive. The faster approach is more vulnerable, though and harder to maintain. The choice is yours.

Related

If each function is deprecated, how to recover the beauty that this function provides?

The typical algorithm of search until found, in PHP and for some arrays where we can't use the language array search functions (I think), could be implemented in this way:
$found = false;
while (list($key, $alreadyRP) = each($this->relatedProducts) && !$found) {
if ($alreadyRP->getProduct()->getId() === $rp->getProduct()->getId()) {
$found = true;
}
}
if (!$found) {
// Do something here
}
Please, take it as pseudocode, I didn't executed it. What I like about it, is that it gracefully stops if it is found what we are looking for.
Now, due to the "each" function is deprecated, I have to code something like this:
$found = false;
foreach ($this->relatedProducts as $alreadyRP) {
if ($alreadyRP->getProduct()->getId() === $rp->getProduct()->getId()) {
$found = true;
break;
}
}
if (!$found) {
// Do something here
}
To put a "break" statement inside a "for" loop is ugly in structured programming. Yes, it is optional, but if we avoid it, the "foreach" will go through all the array, which is not the most efficient way.
Any idea to recover the efficiency and structure that "each" gives in this case?
Thank you.
The beauty of the each() method is in the eye of the beholder, but there are other reasons to prefer foreach, including this interesting bit of information from the RFC that led to the deprecation of each()
The each() function is inferior to foreach in pretty much every imaginable way, including being more than 10 times slower.
If the purpose of the method is to // Do something here if the $rp is not found in $this->relatedProducts, I think a more "beautiful" way to handle it would be to extract the search through related products into a separate method.
protected function inRelatedProducts($id) {
foreach ($this->relatedProducts as $alreadyRP) {
if ($alreadyRP->getProduct()->getId() === $id) {
return true;
}
}
return false;
}
Moving the related products search into a separate method is advantageous because
It separates that functionality from the original method so that it becomes reusable instead of being tied to whatever // Do something here does.
It simplifies the original method so it can focus on its main task
$id = $rp->getProduct()->getId();
if (!$this->inRelatedProducts($id)) {
// Do something here
}
It simplifies the search code because if it's contained in its own method, you can just return true; as soon as you find a match, so you won't need to break, or to keep track of a $found variable at all.
On the other hand, if this was my project I would be looking for a way to remove the need for this method by populating $this->relatedProducts so that it's indexed by ID (assuming ID is unique there) so the determination could be reduced to
$id = $rp->getProduct()->getId();
if (isset($this->relatedProducts[$id])) { ...
If you're looking for a rewrite that doesn't involve extra variables, you can replace the each call with calls to current and next:
do {
$found = (current($this->relatedProducts)->getProduct()->getId() === $rp->getProduct()->getId());
} while (empty($found) && false !== next($array));
This is a mechanical translation, and it relies merely on the definition of each (emphasis mine):
Return the current key and value pair from an array and advance the array cursor
It also suffers the same deficiency of the original each version: it doesn't handle empty arrays.
That said, please don't use each, or any of its siblings, for new code. This from a guy who voted "No" on the RFC! Why? Because the performance sucks:
1017$ cat trial.php
<?php
$array = range(0, 999);
$begin = -microtime(true);
for ($i = 0; $i < 10000; $i++) {
reset($array);
$target = rand(333, 666);
do {
$found = (current($array) === $target);
} while (empty($found) && false !== next($array));
}
printf("%.5f\n", $begin + microtime(true));
$begin = -microtime(true);
for ($i = 0; $i < 10000; $i++) {
$target = rand(333, 666);
foreach ($array as $current) {
if ($current === $target) break;
}
}
printf("%.5f\n", $begin + microtime(true));
1018$ php -d error_reporting=-1 trial.php
8.87178
0.33585
That's nearly nine seconds for the next/current version while not even half a second for the foreach version. Just don't.
It looks like each is basically a version of current() and next()
http://php.net/manual/en/function.current.php
http://php.net/manual/en/function.next.php
each() gives the current array item, and moves to the next index.
current() gives the current array item, but doen't increment the index.
So, you can replace each() with current(), and inside your foreach use next() to shift the index up
next() gives the next item, and increments the index.
while (list($key, $alreadyRP) = current($this->relatedProducts) && !$found) {
if ($alreadyRP->getProduct()->getId() === $rp->getProduct()->getId()) {
$found = true;
}
next($this->relatedProducts);
}

How to check if all the keys of an array are in the expected format

I have an array with multiple keys and I want to be sure they're all in the format I want. For example, if they're all ISO Alpha-2 country codes, I created one function returning true or false to test it.
Basically, I would like to replace this foreach by only one line:
$countriesList = ['US', 'FR', 'CA'];
$correct = true;
foreach ($countriesList as $iso2) {
if (!$this->isIso2($iso2)) {
$correct = false;
}
}
If you really want to replace the foreach with a single call you can use array_walk as follows:
$countriesList = ['US', 'FR', 'CA'];
$correct = true;
array_walk( $countriesList, function( $country ) use (&$correct) { $correct = $correct && $this->isIso2( $country ); } );
The statement use (&$correct) lets the inline callback access the $correct variable (that otherwise would be out of the callback scope).
Note:
you don't have a problem in your original code.
It's readable, concise and I don't see any evident performace flaw.
I would just add a break; to terminate the loop the first time a "false case" is found. There is no reason to keep iterating:
if (!$this->isIso2($iso2)) {
$correct = false;
break; // <--- EXIT THE LOOP
}
In production I would definitely stick with the foreach approach.
Anyway as a programming exercise the solution at the beginning of my answer reduces the loop at a single call.
array_walk iterates over all the items of an array. For each item the inline callback function is invoked with the current item passed as first parameter.

Efficiency of using foreach loops to clear a PHP array's values

Which is more efficient for clearing all values in an array? The first one would require me to use that function each time in the loop of the second example.
foreach ($array as $i => $value) {
unset($array[$i]);
}
Or this
foreach($blah_blah as $blah) {
$foo = array();
//do something
$foo = null;
}
I don't want to use unset() because that deletes the variable.
Like Zack said in the comments below you are able to simply re-instantiate it using
$foo = array(); // $foo is still here
If you want something more powerful use unset since it also will clear $foo from the symbol table, if you need the array later on just instantiate it again.
unset($foo); // $foo is gone
$foo = array(); // $foo is here again
If we are talking about very large tables I'd probably recommend
$foo = null;
unset($foo);
since that also would clear the memory a bit better. That behavior (GC) is however not very constant and may change over PHP versions. Bear in mind that re-instantiating a structure is not the same as emptying it.
If you just want to reset a variable to an empty array, you can simply reinitialize it:
$foo = array();
Note that this will maintain any references to it:
$foo = array(1,2,3);
$bar = &$foo;
// ...
$foo = array(); // clear array
var_dump($bar); // array(0) { } -- bar was cleared too!
If you want to break any references to it, unset it first:
$foo = array(1,2,3);
$bar = &$foo;
// ...
unset($foo); // break references
$foo = array(); // re-initialize to empty array
var_dump($bar); // array(3) { 1, 2, 3 } -- $bar is unchanged
Unsetting the variable is a nice way, unless you need the reference of the original array!
To make clear what I mean:
If you have a function wich uses the reference of the array, for example a sorting function like
function special_sort_my_array(&$array)
{
$temporary_list = create_assoziative_special_list_out_of_array($array);
sort_my_list($temporary_list);
unset($array);
foreach($temporary_list as $k => $v)
{
$array[$k] = $v;
}
}
it is not working! Be careful here, unset deletes the reference, so the variable $array is created again and filled correctly, but the values are not accessable from outside the function.
So if you have references, you need to use $array = array() instead of unset, even if it is less clean and understandable.
I'd say the first, if the array is associative. If not, use a for loop:
for ($i = 0; $i < count($array); $i++) { unset($array[$i]); }
Although if possible, using
$array = array();
To reset the array to an empty array is preferable.
Isn't unset() good enough?
unset($array);
How about $array_name = array(); ?
Use array_splice to empty an array and retain the reference:
array_splice($myArray, 0);
i have used unset() to clear the array but i have come to realize that unset() will render the array null hence the need to re-declare the array like for example
<?php
$arr = array();
array_push($arr , "foo");
unset($arr); // this will set the array to null hence you need the line below or redeclaring it.
$arr = array();
// do what ever you want here
?>
Maybe simple, economic way (less signs to use)...
$array = [];
We can read in php manual :
As of PHP 5.4 you can also use the short array syntax, which replaces array() with [].
I see this questions is realla old, but for that problem I wrote a recursive function to unset all values in an array. Recursive because its possible that values from the given array are also an array. So that works for me:
function empty_array(& $complete_array) {
foreach($complete_array as $ckey => $cvalue)
{
if (!is_array($cvalue)) {
$complete_array[$ckey] = "";
} else {
empty_array( $complete_array[$ckey]);
}
}
return $complete_array;
}
So with that i get the array with all keys and sub-arrays, but empty values.
The unset function is useful when the garbage collector is doing its rounds while not having a lunch break;
however unset function simply destroys the variable reference to the data, the data still exists in memory and PHP sees the memory as in use despite no longer having a pointer to it.
Solution:
Assign null to your variables to clear the data, at least until the garbage collector gets a hold of it.
$var = null;
and then unset it in similar way!
unset($var);
The question is not really answered by the posts. Keeping the keys and clearing the values is the focus of the question.
foreach($resultMasterCleaned['header'] as $ekey => $eval) {
$resultMasterCleaned[$key][$eval] = "";
}
As in the case of a two dimensional array holding CSV values and to blank out a particular row. Looping through seems the only way.
[] is nearly 30% faster then as array()
similar as pushing new element to array
$array[] = $newElement then array_push($array, $newElement)
(keep in mind array_push is slower only for single or 2 new elements)
Reason is we skip overhead function to call those
PHP array vs [ ] in method and variable declaration

How do I use array_filter() for functional programming in PHP?

Say I have an array of tags
$all_tags = array('A', 'B', 'C');
And I want to create a set of URLs with $_GET variables.
I'd like the links to be:
'A' linking to "index.php?x[]=B&x[]=C"
'B' linking to "index.php?x[]=A&x[]=C"
etc. ($_GET is an array with all elements except for "current" element)
(I know there's an easier way to implement this: I'm actually simplifying a more complex situation)
I'd like to use array_filter() to solve this.
Here's my attempt:
function make_get ($tag) { return 'x[]=' . $tag; }
function tag_to_url ($tag_name) {
global $all_tags;
$filta = create_function('$x', 'global $all_tags; return ($x != $tag_name);');
return 'index.php?' . implode('&', array_map("make_get", array_filter($all_tags, "filta")));
}
print_r(array_map("", $all_tags));
But it doesn't work. I have a suspicion that maybe it has to do with how maps and filters in PHP actually mutate the data structure themselves, and return a boolean, instead of using a functional style, where they don't mutate and return a new list.
I am also interested in other ways to make this code more succinct.
Here's an alternative approach:
// The meat of the matter
function get_link($array, $tag) {
$parts = array_reduce($array, function($result, $item) use($tag)
{
if($item != $tag) $result[] = 'x[]='.$tag;
return $result;
});
return implode('&', $parts);
}
// Test driver
$all_tags = array('A', 'B', 'C');
echo get_link($all_tags, 'A');
echo "\n";
echo get_link($all_tags, 'B');
echo "\n";
echo get_link($all_tags, 'C');
echo "\n";
It's simply one call to array_reduce, and then an implode to pull the results together into a query string.
Something approaching real support for functional programming styles in PHP is very, very new--it was only in PHP 5.3 that functions became first class and anonymous functions were possible.
BTW, you should never use create_function(). What it really does is define a new function in the global namespace (which will never be garbage-collected!), and it uses eval() behind the scenes.
If you have PHP 5.3 or greater, you can do this:
$all_tags = array('A', 'B', 'C');
function is_not_equal($a, $b) {
return $a != $b;
}
function array_filter_tagname($alltags, $name) {
$isNotEqualName = function($item) use ($name){
return is_not_equal($item, $name);
};
// array_merge() is ONLY to rekey integer keys sequentially.
// array_filter() preserves keys.
return array_merge(array_filter($alltags, $isNotEqualName));
}
function make_url($arr) {
return 'input.php?'.http_build_query(array('x'=>$arr));
}
$res = array_filter_tagname($all_tags, 'B');
print_r($res);
print_r(make_url($res));
If you have a PHP < 5.3, you should use a class+object for your closures instead of create_function().
class NotEqualName {
protected $otheritem;
function __construct($otheritem) { // with PHP 4, use "function NotEqualName($otheritem) {"
$this->otheritem = $otheritem;
}
function compare($item) {
return $item != $this->otheritem;
}
}
function array_filter_tagname_objectcallback($alltags, $name) {
$isNotEqualName = new NotEqualName($name);
return array_merge(array_filter($alltags, array($isNotEqualName,'compare')));
}
In general, however, PHP is not very well suited to a functional style, and for your particular task using array_filter() is not very idiomatic PHP. array_diff() is a better approach.
Based on an answer I gave in the comments (shown here):
<?php
$all_tags = array('A', 'B', 'C');
function tag_to_url($tag_name)
{
global $all_tags;
$remaining_tags = array_diff($all_tags, array($tag_name));
return sprintf('index.php?%s',
http_build_query(array('x'=>array_values($remaining_tags))));
}
echo tag_to_url('B'); // index.php?x%5B0%5D=A&x%5B1%5D=C
// basically: index.php?x[0]=A&x[1]=C
Basically, use array_diff to remove the entry from the array (instead of filtering), then pass it off to the http_build_query to come up with a valid URL.
I'm just going to answer the "why it doesnt work" part.
$filta = create_function('$x', 'global $all_tags; return ($x != $tag_name);');
The tag_name variable is undefined in your lambda function's scope. Now, it is defined in the creating functions scope(tag_to_url). You can't exactly use the global keyword here either, because $tag_name isn't in the global scope, it is in the local tag_to_url scope. You could declare the variable in the global scope and then it could work, but considering you're fond of functional approaches, I doubt you like global variables :)
You could do trickery with string concatenation and var_export($tag_name) and pass that to create_function() if you wanted, but there's other better ways to achieve your goals.
Also, as an aside, I'm guessing you might benefit from turning up php's error reporting level while developing. php would have thrown undefined variable notices at you, which helps debugging and understanding.
// ideally set these in php.ini instead of in the script
error_reporting(E_ALL);
ini_set('display_errors', 1);

Cast to array VS is_array()

Does anyone know of any issues, performance or other, that can occur by casting a variable to an array instead checking it first?
// $v could be a array or string
$v = array('1','2','3');
OR
$v = '1';
instead of:
if (is_array($v)) foreach ($v as $value) {/* do this */} else {/* do this */}
I've started to use:
foreach((array) $v as $value) {
// do this
}
It stops the repetition of code quite a bit - but performance is on my mind, not ugly code.
Also, does anyone know how php handles casting an array to an array? No errors are thrown but does the php engine check if it's a array, then return a result before doing the cast process?
First: Premature optimization is the root of all evil. Never let performance influence your coding style!
Casting to an array allows to some nice tricks, when you need an array, but want to allow a single value
$a = (array) "value"; // => array("value")
Note, that this may lead to some unwanted (or wanted, but different) behaviours
$a = new stdClass;
$a->foo = 'bar';
$a = (array) $a; // => array("foo" => "bar");
However, this one
if(is_array($v)) {
foreach($v as $whatever)
{
/* .. */
}
} else {
/* .. */
}
allows you to decide what should happen for every single type, that can occur. This isn't possible, if you cast it "blindly" to an array.
In short: Just choose the one, that better fits the needs of the situation.
As Felix Kling says above, the best solution is to have your data come from a source that guarantees its type. However, if you can't do that, here's a comparison for 1000000 iterations:
check first: 2.244537115097s
just cast: 1.9428250789642s
(source)
Just casting without checking (using in_array) seems to be (slightly) quicker.
Another option here is this (this can be done inline as well, of course, to avoid the function call):
function is_array($array) {
return ($array."" === "Array");
}
This seems to be slightly faster than is_array, but your mileage may vary.
The problem with casting to an array like this (which is also an option)
if ((array) $v === $v)
Is that it's faster than is_array for small arrays, but disastrously slower for large ones.

Categories