I have a function that searches a multidimensional array for a key, and returns the path
inside the array to my desired key as a string.
Is there any way I can use this string in php to reach this place in my original array, not to get to the value but to make changes to this specific bracnch of the array?
An example:
$array = array('first_level'=>array(
'second_level'=>array(
'desired_key'=>'value')));
in this example the function will return the string:
'first_level=>second_level=>desired_key'
Is there a way to use this output, or format it differently in order to use it in the following or a similar way?
$res = find_deep_key($array,'needle');
$array[$res]['newkey'] = 'injected value';
Thanks
If the keys path is safe (e.g. not given by the user), you can use eval and do something like:
$k = 'first_level=>second_level=>desired_key';
$k = explode('=>', $k);
$keys = '[\'' . implode('\'][\'', $k) . '\']';
eval('$key = &$array' . $keys . ';');
var_dump($key);
I think you want to do a recursive search in the array for your key? Correct me if i am wrong.
Try this
function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value OR (is_array($value) && recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
Taken from here http://in3.php.net/manual/en/function.array-search.php#91365
You need something like:
find_key_in_array($key, $array, function($foundValue){
// do stuff here with found value, e.g.
return $foundValue * 2;
});
and the implementation would be something like:
function find_key_in_array($key, $array, $callback){
// iterate over array fields recursively till you find desired field, then:
...
$array[$key] = $callback($array[$key]);
}
If you need to append some new sub-array into multidimensional complex array and you know where exactly it should be appended (you have path as a string), this might work (another approach without eval()):
function append_to_subarray_by_path($newkey, $newvalue, $path, $pathDelimiter, &$array) {
$destinationArray = &$array;
foreach (explode($pathDelimiter, $path) as $key) {
if (isset($destinationArray[$key])) {
$destinationArray = &$destinationArray[$key];
} else {
$destinationArray[$newkey] = $newvalue;
}
}
}
$res = find_deep_key($array,'needle');
append_to_subarray_by_path('newkey', 'injected value', $res, '=>', $array);
Of course, it will work only if all keys in path already exist. Otherwise it will append new sub-array into wrong place.
just write a function that takes the string and the array. The function will take the key for each array level and then returns the found object.
such as:
void object FindArray(Array[] array,String key)
{
if(key.Length == 0) return array;
var currentKey = key.Split('=>')[0];
return FindArray(array[currentKey], key.Remove(currentKey));
}
Related
Is there no built in function for this? I was only able to find
array_key_first and last;
I made this function , that sort of works, but still...
/**
* Get array key from position
*/
function get_array_key($arr, $pos = 0)
{
foreach ($arr as $key => $value) {
$mykey[] = $key;
}
if (isset($mykey[$pos])) return $mykey[$pos];
}
You can just use array_keys:
echo array_keys($arr)[$pos];
Here's a demo comparing it to your function.
I have an array which consists of arrays. So, now suppose I want to retrieve the sku and price whose
key value is 2=>5 and 3=>7 so it should return price=>13 and sku=>bc i.e. that array whose index is at 1 in the array.
Hi I would probably try the following (Same as Riziers comment)
foreach($array as $key => $item) {
if($item[2] == 5 && $item[3] == 7) {
// return price
return $item;
}
}
There is a function array_search, which does what you want but for simple values. You can define your own function that will take not $needle, but callable predicate:
function array_search_callback(callable $predicate, array $array)
{
foreach ($array as $key => $item) {
if ($predicate($item)) {
return $key;
}
}
return false;
}
Having this function your example can be done like this:
$key = array_search_callback(function ($item) {
return $item[2] === '5' && $item[3] === '7';
}, $array);
$result = $key === false ? null : $array[$key];
I could simply return an item from the search function. But to be consistent with the original search function, I am returning the index.
As array_search_callback takes callable as an argument you can provide any criteria you want without the need of modifying the function itself.
Here is working demo.
I am not sure I am using the right terms even here, but I will try and explain. I am using PHP's array_filter function to filter products and it calls back to a custom function I made where I add the filter(s). I can do this hard-coded very easy, but I obviously want it to be dynamic:
To cut a long story short, the custom filter function returns to the array_filter() function like so:
return ($arr['colour']=='Red' || $arr['colour']=='White');
This works fine if hardcoded like the above, and filters the array as expected to only show products that are red or white. However, I need this to be dynamic.
So how can I construct a string of values and then use this in the return statement?
For example:
$var = "$arr['colour'] == 'Red' || $arr['colour'] == 'White'";
return ($var);
It does not work. I have tried using eval() (I don't want to use this anyway!), and it didn't work still.
I have a loop as follows constructing the string from an array:
// $value=array of filters e.g colour=Black, colour=Red
$filterparts = explode("=", $value);
$filters[] = '$arr[\'' . $filterparts[0] . '\'] == \'' . $filterparts[1] . '\'';
// Creates array e.g $arr['colour'] = 'Red'
$imploded_filter = implode(" || ", $uniquefilters);
// Creates string, e.g. $arr['colour'] = 'Red' || $arr['colour'] = 'White'
So if I echo $imploded_filter I get the extract string I would like to return:
echo $imploded_filter;
// Outputs $arr['colour'] = 'Red' || $arr['colour'] = 'White'
However if I do
return($imploded_filter);
it obviously isn't evaluating the string as hard code, so what can I do? Do I need to do something to the string or return it a different way, or construct the code I need to return in a totally different way?
Array keys can be specified dynamically. There isn't any need for eval():
$value = $array[$key];
You can build a list of filters and match each of them in the array_filter() callback:
$filters = array(
array('colour', array('white', 'blue')), // Multiple accepted values (OR)
array('material', 'Fine Bone China'), // Single accepted value
);
$filtered = array_filter($products, function ($item) use ($filters) {
// Match all filters
foreach ($filters as $filter) {
// Detect multi-value filter
$isArrayFilter = is_array($filter[1]);
if (
// Check if multi-value filter doesn't match
$isArrayFilter && !in_array($item[$filter[0]], $filter[1])
// Check if a single-value filter doesn't match
|| !$isArrayFilter && $item[$filter[0]] != $filter[1]
) {
// Filter doesn't match - exclude the item
return false;
}
}
// All filters match - include the item
return true;
});
$colors = ['Red', 'White'];
$products = array_filter($products, function ($product) use ($colors) {
return in_array($product['color'], $colors);
});
There's virtually never any reason or need to "dynamically create PHP source code". There's always an operation that can do what you want on any number of elements without needing to concatenate || operators. Here in_array is a perfectly fine function to test one value against many. You can pass in the colors array dynamically using use ($colors).
The sanest workaround for ancient PHP versions is to approximate the anonymous callback with a class:
class InArrayFilterCallback {
public $data = array();
public $key;
public __construct($data, $key) {
$this->data = $data;
$this->key = $key;
}
public callback($item) {
return in_array($item[$this->key], $this->data);
}
}
$products = array_filter($products, array(new InArrayFilterCallback($colors, 'color'), 'callback'));
Of course, you could also just use a simple foreach loop instead...
Use the in_array function, like so:
$filters = [
'colour' => [
'red',
'blue',
]
];
array_filter($list, function ($item) use ($filters) {
foreach ($filters as $index => $filter) {
if (!in_array($item[$index], $filter)) {
return false;
}
}
return true;
});
It is never a good idea to make a string and eval'uate it.
If certain elements are contained in an array, I want them moved to the start of it.
At first I used a bunch of array_diff_keys to get it to work, but I wanted something more elegant. So I tried using uksort with a callback, but perhaps I'm doing it wrong because it's not working.
I tried this, it's a method of my helper class, but it's not working.
$good_elements = array('sku','name','type','category','larping');
$test_array = array('sku','name','asdf','bad_stuff','larping','kwoto');
$results = helper::arrayPromoteElementsIfExist($test_array,$good_elements,false);
public static function arrayPromoteElementsIfExist($test_array,$promote_elements,$use_keys = false) {
foreach(array('test_array','promote_elements') as $arg) {
if(!is_array($$arg)) {
debug::add('errors',__FILE__,__LINE__,__METHOD__,'Must be array names',$$arg);
return false;
}
}
if(!$use_keys) {
$test_array = array_flip($test_array); // compare keys
$promote_elements = array_flip($promote_elements); // compare keys
}
uksort($test_array,function($a,$b) use($promote_elements) {
$value1 = intval(in_array($a, $promote_elements));
$value2 = intval(in_array($b,$promote_elements));
return $value1 - $value2;
});
if(!$use_keys) {
$test_array = array_flip($test_array);
}
return $test_array;
}
Fairly quick and dirty but here you go.
function promoteMembers($input, $membersToPromote)
{
$diff = array_diff($input, $membersToPromote);
return array_merge($membersToPromote, $diff);
}
Assuming I understood what you wanted to do.
Example output: for your verification.
I need to do fast lookups to find if an array exists in an array. If I knew the depth of the array It would be easy - and fast!
$heystack['lev1']['lev2']['lev3'] = 10; // $heystack stores 10,000s of arrays like this
if(isset($heystack[$var1][$var2][$var3])) do something...
How would you do this dynamically if you don't know the depth? looping and searching at each level will be too slow for my application.
Your question has already the answer:
if (isset($heystack[$var1][$var2][$var3]))
{
# do something...
}
If you don't know the how many $var1 ... $varN you have, you can only do it dynamically which involves either looping or eval and depends if you need to deal with string or numerical keys. This has been already asked and answered:
Loop and Eval: use strings to access (potentially large) multidimensional arrays (and that's only one of the many)
If you are concerned about speed, e.g. if the array is always the same but you need to query it often, create a index first that has compound keys so you can more easily query it. That could be either done by storing all keys while traversing the array recursively:
class CompoundKeys extends RecursiveIteratorIterator
{
private $keys;
private $separator;
public function __construct($separator, RecursiveIterator $iterator, $mode = RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
$this->separator = $separator;
parent::__construct($iterator, $mode, $flags);
}
public function current()
{
$current = parent::current();
if (is_array($current))
{
$current = array_keys($current);
}
return $current;
}
public function key()
{
$depth = $this->getDepth();
$this->keys[$depth] = parent::key();
return implode('.', array_slice($this->keys, 0, $depth+1));
}
}
Usage:
$it = new CompoundKeys('.', new RecursiveArrayIterator($array));
$compound = iterator_to_array($it, 1);
isset($compound["$var1.$var2.$var3"]);
Alternatively this can be done by traversing recursively and referencing the original arrays values:
/**
* create an array of compound array keys aliasing the non-array values
* of the original array.
*
* #param string $separator
* #param array $array
* #return array
*/
function array_compound_key_alias(array &$array, $separator = '.')
{
$index = array();
foreach($array as $key => &$value)
{
if (is_string($key) && FALSE !== strpos($key, $separator))
{
throw new InvalidArgumentException(sprintf('Array contains key ("%s") with separator ("%s").', $key, $separator));
}
if (is_array($value))
{
$subindex = array_compound_key_alias($value, $separator);
foreach($subindex as $subkey => &$subvalue)
{
$index[$key.$separator.$subkey] = &$subvalue;
}
}
else
{
$index[$key] = &$value;
}
}
return $index;
}
Usage:
$index = array_compound_key_alias($array);
isset($index["$var1.$var2.$var3"]);
You'll need some sort of looping but you won't need to traverse the entire depth. You can simply use a function that does the equivalent of $heystack[$var1][$var2][$var3], but dynamically:
$heystack['lev1']['lev2']['lev3'] = 10;
echo getElement($heystack, array('lev1', 'lev2', 'lev3')); // you could build second parameter dynamically
function getElement($array, $indexes = array())
{
foreach ($indexes as $index) {
$array = $array[$index];
}
return $array;
}
// output: 10
You'll need to put in some defense mechanisms to make the function more robust (for elements/indexes that don't exist) but this is the basic approach.