How to map array depth? - php

I'm not sure how to call this, and I can't find what I'm looking for because I must be looking for the wrong keywords, so even the right search term would help me out here:
I have a $map array and a $data array. The $data array is a dynamic array based on a xml that is parsed. Simplified Example:
$data = array('a' => array(0=> array('hello'=>'I want this data')));
$map = array('something' => 'a.0.hello');
Now I'd like to set $test to the value of $data['a']['0']['hello'] by somehow navigating there using $map['something']. The idea behind this is to create a mapping array so that no code changes are required if the xml format is to be changed, only the mapping array. Any help in the good direction is very much appreciated :)

// Set pointer at top of the array
$path = &$data;
// Asume that path is joined by points
foreach (explode('.', $map['something']) as $p)
// Make a next step
$path = &$path[$p];
echo $path; // I want this data

There's not really a good way to programmatically access nested keys in arrays in PHP (or any other language I can think of, actually).
function getDeepValue(&$arr, $keys) {
if (count($keys) === 0)
return $arr;
else
return getDeepValue($arr[$keys[0]], array_slice($keys, 1));
}
$data = ['a' => [0=> ['hello'=>'I want this data']]];
$map = ['something' => getDeepValue($data, ['a', '0', 'hello'])];
var_dump($map);
// array(1) {
// ["something"]=>
// string(16) "I want this data"
// }
If you want to use the string of keys, just use
getDeepValue($arr, explode('.', 'a.0.hello'))
If that's a common operation you have to do, you could add another layer of abstraction that accepts string-based lookup of deep values.

Related

Variable Variables for Array Key

Currently I am attempting to call a multidimensional array, using a string as a key or keys. I would like to use the following code, but I think the key is being interpreted as a string. Any solution?
$data= [];
$data['volvo'] = "nice whip";
$test = "['volvo']";
$data['drivers']['mike'] = "decent";
$test2 = "['drivers']['mike']";
echo $data$test; // should read 'nice whip'
echo $data$test2; // should read 'decent'
You just use the variable (which should just be the string and not PHP syntax) in place of the string literal.
$cars = [];
$cars['volvo'] = 'nice whip';
$test = 'volvo';
echo $cars[$test];
If you need a dynamic array access solution, you could also write a function, which does the actual array access like this:
function path($array, $path) {
$path = is_array($path) ? $path : explode('.', $path);
$current = $array;
while (count($path)) {
$seg = array_shift($path);
if (!isset($current[$seg])) throw new Exception('Invalid path segment: ' . $seg);
$current = $current[$seg];
}
return $current;
}
In your case, this would look like this
echo path($data, 'volvo');
echo path($data, 'drivers.mike');
or
echo path($data, ['volvo']);
echo path($data, ['drivers', 'mike']);
The problem is you can't pass multiple levels in one string like that. (If so, PHP would have to start looking for code fragments inside string array keys. And how would it know whether to interpret them as fragments and then split the string key up, or keep treating it as one string??)
Alt 1
One solution is to change the structure of $data, and make it a single level array. Then you supply keys for all the levels needed to find your data, joined together as a string. You would of course need to find a separator that works in your case. If the keys are plain strings then something simple like underscore should work just fine. Also, this wouldn't change the structure of your database, just the way data is stored.
function getDbItem($keys) {
// Use this to get the "old version" of the key back. (I.e it's the same data)
$joinedKey = "['".implode("'],['", $keys)."']";
$joinedKey = implode('_', $keys);
return $data[$joinedKey];
}
// Example
$data = [
'volvo' => 'nice whip',
'drivers_mike' => 'decent'
];
var_dump(getDbItem(['drivers', 'mike'])); // string(6) "decent"
Alt 2
Another way is to not change number of levels in $data, but simply traverse it using the keys passed in:
$tgt = $data;
foreach($keys as $key) {
if (array_key_exists($key, $tgt)) {
$tgt = $tgt[$key];
}
else {
// Non existing key. Handle properly.
}
}
// Example
$keys = ['drivers', 'mike'];
// run the above code
var_dump($tgt); // string(6) "decent"

PHP: use return as key / value pair for a parent array

Well, after I rewrote my problem description 2 times i'll do it somehow less abstract now.
I need a way to return data within an array so that this array accepts this returned data as native key / value pair.
Illustration:
simple array:
$a = array('a' => 'b');
I could access the value of 'a' by simply calling $a['a']... clearly
I now need a way to exactly get this result via a function call or hack or something!
something like
$a = array(foo('a','b'));
the problem is, when "foo" return an array, the structure of my $a (array) will look like this:
array(array('a','b')) and not like array('a' => 'b')
and will prevent me from being able to access the data via $a['a']
The reason I need this is that I instance objects on demand within this array definition and also modify these instances on the fly, but I do need 2 different returns (of functions) of one and the same object and don't want to copy past half the initialization. Further more I don't want to first declare object instances outside the array because this would be a absolute overkill and would perfectly destroy all readability.
So.. is there a possibility to extract returned values to native language features?
PS. I also don't want to iterate over the array to re-process the data
$a = foo('a','b');
with
function foo($x,$y) {
return array($x => $y);
}
If foo() is returning an array, why not;
$a = foo('a','b');
Seems simple enough, right?
Easiest way:
$a[]= foo();
function foo() {
return array(1,2);
}
Or use reference:
$value = NULL;
$a = array(foo($value), $value);
with
function foo(&$value) {
$value = 'b';
return 'a';
}
function foo($key) {
$r = array(
'a' => 'b',
$key2 => $value2,
$key3 => $value3,
$key4 => $value4
// ...
);
return $r[$key];
}
$a = foo('a'); // means $a = 'b';
Do it solves your problem ?

Most efficient way to empty an array

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.

Split a string to form multidimensional array keys?

I'm creating JSON encoded data from PHP arrays that can be two or three levels deep, that look something like this:
[grandParent] => Array (
[parent] => Array (
[child] => myValue
)
)
The method I have, which is simply to create the nested array manually in the code requires me to use my 'setOption' function (which handles the encoding later) by typing out some horrible nested arrays, however:
$option = setOption("grandParent",array("parent"=>array("child"=>"myValue")));
I wanted to be able to get the same result by using similar notation to javascript in this instance, because I'm going to be setting many options in many pages and the above just isn't very readable, especially when the nested arrays contain multiple keys - whereas being able to do this would make much more sense:
$option = setOption("grandParent.parent.child","myValue");
Can anyone suggest a way to be able to create the multidimensional array by splitting the string on the '.' so that I can json_encode() it into a nested object?
(the setOption function purpose is to collect all of the options together into one large, nested PHP array before encoding them all in one go later, so that's where the solution would go)
EDIT: I realise I could do this in the code:
$options['grandparent']['parent']['child'] = "myValue1";
$options['grandparent']['parent']['child2'] = "myValue2";
$options['grandparent']['parent']['child3'] = "myValue3";
Which may be simpler; but a suggestion would still rock (as i'm using it as part of a wider object, so its $obj->setOption(key,value);
This ought to populate the sub-arrays for you if they haven't already been created and set keys accordingly (codepad here):
function set_opt(&$array_ptr, $key, $value) {
$keys = explode('.', $key);
// extract the last key
$last_key = array_pop($keys);
// walk/build the array to the specified key
while ($arr_key = array_shift($keys)) {
if (!array_key_exists($arr_key, $array_ptr)) {
$array_ptr[$arr_key] = array();
}
$array_ptr = &$array_ptr[$arr_key];
}
// set the final key
$array_ptr[$last_key] = $value;
}
Call it like so:
$opt_array = array();
$key = 'grandParent.parent.child';
set_opt($opt_array, $key, 'foobar');
print_r($opt_array);
In keeping with your edits, you'll probably want to adapt this to use an array within your class...but hopefully this provides a place to start!
What about $option = setOption("grandParent", { parent:{ child:"myValue" } });?
Doing $options['grandparent']['parent']['child'] will produce an error if $options['grandparent']['parent'] was not set before.
The OO version of the accepted answer (http://codepad.org/t7KdNMwV)
$object = new myClass();
$object->setOption("mySetting.mySettingsChild.mySettingsGrandChild","foobar");
echo "<pre>".print_r($object->options,true)."</pre>";
class myClass {
function __construct() {
$this->setOption("grandparent.parent.child","someDefault");
}
function _setOption(&$array_ptr, $key, $value) {
$keys = explode('.', $key);
$last_key = array_pop($keys);
while ($arr_key = array_shift($keys)) {
if (!array_key_exists($arr_key, $array_ptr)) {
$array_ptr[$arr_key] = array();
}
$array_ptr = &$array_ptr[$arr_key];
}
$array_ptr[$last_key] = $value;
}
function setOption($key,$value) {
if (!isset($this->options)) {
$this->options = array();
}
$this->_setOption($this->options, $key, $value);
return true;
}
}
#rjz solution helped me out, tho i needed to create a array from set of keys stored in array but when it came to numerical indexes, it didnt work. For those who need to create a nested array from set of array indexes stores in array as here:
$keys = array(
'variable_data',
'0',
'var_type'
);
You'll find the solution here: Php array from set of keys

Removing php array duplicates with regard to a few specific indexes

Suppose I have a multi-dimensional array with 100's of sub-arrays. The sub-arrays always have at least 3 indexes, but can have more. I'd like to remove from the big array all the sub-arrays which are duplicates of others in regards to those three indexes. Example with an array that only has two sub-arrays:
array(array(0 => 'a', 1=> 'b', 2 => 'c', 3 => 'd'), array(0 => 'a', 1=> 'b', 2=> 'c', 3=> 'z'))
one of the sub-arrays would be removed, because the first 3 indexes match even though the 4th does not.
I'm looking for the most elegant/efficient solution.
/**
* Create Unique Arrays using an md5 hash
*
* #param array $array
* #return array
*/
function arrayUnique($array, $preserveKeys = false)
{
$arrayRewrite = array();
$arrayHashes = array();
foreach($array as $key => $item) {
$hash = md5(serialize($item));
if (!isset($arrayHashes[$hash])) {
$arrayHashes[$hash] = $hash;
if ($preserveKeys) {
$arrayRewrite[$key] = $item;
} else {
$arrayRewrite[] = $item;
}
}
}
return $arrayRewrite;
}
$uniqueArray = arrayUnique($array);
var_dump($uniqueArray);
FROM: http://www.phpdevblog.net/2009/01/using-array-unique-with-multidimensional-arrays.html
Removed comments to give people incentive to visit site - I've used this on a few occasions.
Hope that helps!
EDIT: although not a solution to this particular problem in that you require matching the first 3 indexes, it is still nontheless a very good solution to the general question: how do I use array_unique() on a multidimensional array.
If somebody could pop along and edit for your purposes, all the better!
Zenph gets it 90% right, but he wanted to only look at the first 3 elements as unique. You can use the function below in conjunction with Zenph's code right before the serialize to only look at the first three elements.
function firstThree($array)
{
$retArray = array();
array_push($retArray, $array[1], $array[2], $array[3]);
return $retArray;
}
This will do the trick. Not the most elegant example due to the free function with a static variable, but you can make it more elegant by using a lambda or an instance of a class for the callback target.
function filter($subArray) {
static $seenKeys = null;
if ($seenKeys === null) {
$seekKeys = array();
}
// I 'm just selecting the "three first" indexes here,
// you can change it to better suit your needs
$thisKey = serialize(array_slice($subArray, 0, 3));
if (isset($seenKeys[$thisKey])) {
return false;
}
else {
return $seenKeys[$thisKey] = true;
}
}
$result = array_filter($inputArray, 'filter');
I think this example is as fast as you can go in PHP without making assumptions about the type and/or values of the first three items in each subarray. If any such assumption can be made, then at the very least the serialize call can be replaced with something more appropriate. I imagine that this would speed up the process quite a bit.

Categories