Php algorithm - How to achieve that without eval - php

I have a class that keeps data stores/access data by using words.separated.by.dots keys and it behaves like the following:
$object = new MyArray()
$object->setParam('user.name','marcelo');
$object->setParam('user.email','some#email.com');
$object->getParams();
/*
array(
'user' => array(
'name' => 'marcelo',
'email' => 'some#email.com'
)
);
*/
It is working, but the method unsetParam() was horribly implemented. That happened because i didn't know how to achieve that without eval() function. Although it is working, I found that it was a really challenging algorithm and that you might find fun trying to achieve that without eval().
class MyArray {
/**
* #param string $key
* #return Mura_Session_Abstract
*/
public function unsetParam($key)
{
$params = $this->getParams();
$tmp = $params;
$keys = explode('.', $key);
foreach ($keys as $key) {
if (!isset($tmp[$key])) {
return $this;
}
$tmp = $tmp[$key];
}
// bad code!
$eval = "unset(\$params['" . implode("']['", $keys) . "']);";
eval($eval);
$this->setParams($params);
return $this;
}
}
The test method:
public function testCanUnsetNestedParam()
{
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
'2' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
)
),
'2' => 'something'
);
$session = $this->newSession();
$session->setParams($params);
unset($params['1']['1']['1']);
$session->unsetParam('1.1.1');
$this->assertEquals($params, $session->getParams());
$this->assertEquals($params['1']['1']['2'], $session->getParam('1.1.2'));
}

Is this it?
<?php
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
'2' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
)
),
'2' => 'something'
);
function unsetParam( &$array, $paramString ) {
$cur =& $array;
$splitted = explode( ".", $paramString );
$len = count( $splitted ) - 1;
for( $i = 0; $i < $len; ++$i ) {
if( isset( $cur[ $splitted[ $i ] ] ) ) {
$cur =& $cur[ $splitted[ $i ] ];
}
else {
return false;
}
}
unset( $cur[ $splitted[$i] ] );
}
unsetParam( $params, "1.1.1");
print_r( $params );
/*
Array
(
[1] => Array
(
[1] => Array
(
[2] => Array
(
[1] => one
[2] => two
[3] => three
)
)
)
[2] => something
)
*/

You could make your code easier if you only split to a multidimension array in your getParams method:
class MyArray {
private $params = array();
public function setParam($key, $value) {
$this->params[$key] = $value;
}
/**
* #param string $key
* #return Mura_Session_Abstract
*/
public function unsetParam($key)
{
unset($this->params[$key]);
return $this;
}
public function getParams() {
$retval = array();
foreach ($this->params as $key => $value) {
$aux = &$retval;
foreach (explode(".", $key) as $subkey) {
if (!isset($aux[$subkey])) $aux[$subkey] = array();
$aux = &$aux[$subkey];
}
$aux = $value;
}
return $retval;
}
}

#gustavotkg and #Esailija have both offered some great ideas. Here is another simple, easy to understand, and short approach that avoids unset() altogether (which can get quirky in some cases).
This would, of course, be most useful when $params is limited to less than, say, 1k-10k of values (which starts to get a bit pricey in the CPU/memory dept):
<?php
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one-one',
'2' => 'one-two',
'3' => 'one-three',
),
'2' => array(
'1' => 'two-one',
'2' => 'two-two',
'3' => 'two-three',
),
)
),
'2' => 'something'
);
function filterParams($params, $refKey, $base = '') {
$newvals = array();
foreach($params as $k=>$v) {
$joinedKey = $base? $base . '.' . $k : $k;
if( $joinedKey != $refKey ) {
$newvals[$k] = is_array($v)? filterParams($v, $refKey, $joinedKey) : $v;
}
}
return $newvals;
}
var_dump(filterParams($params, '1.1.2'));

Related

Method to change specific value inside multidimensional array

Given a class with a multidimensional array as private property.
I'm trying to write a method setValue() which could change any value of this private array by passing:
some kind of path to a specific value (e.g. $path = ['lorem' => ['ipsum' => ['dolor' => ['sit' => null]]]];)
the new value
...
Class Tree {
private $tree = array(
'lorem' => array(
'ipsum' => array(
'dolor' => array(
'sit' => 'old value'
)
),
'amet' => array(
'consetetur' => array(
'sadipscing' => 'another value'
)
)
)
);
// setValue has to be a recursive function I guess
public function setValue($path, $value) {
// ???
}
public function getTree() {
return $this->tree;
}
}
Thanks to a comment of Gabriel on php.net I was able to find a working solution.
You can see my solution among the answers below.
What I actualy want to know: What are alternative approaches?
There are some recursive array built-in functions that may help you here:
<?php
class Tree
{
private $tree =
[
'foo' =>
[
'bar' => 'baz',
'qux' => 'quux'
]
];
public function replaceLeaf($key, $value) {
array_walk_recursive(
$this->tree,
function(&$item, $index) use ($key, $value) {
if($key === $index)
$item = $value;
}
);
}
public function replace($value) {
$this->tree = array_replace_recursive(
$this->tree,
$value
);
}
public function getTree()
{
return $this->tree;
}
}
$tree = new Tree;
$tree->replaceLeaf('bar', 'stool');
$tree->replace(
['foo' => ['qux' => 'quack']]
);
var_export($tree->getTree());
Output:
array (
'foo' =>
array (
'bar' => 'stool',
'qux' => 'quack',
),
)
However looking at your original question targeting the array is likely an easier syntax, especially if you are only changing one attribute at a time. You'd have to change the visibility of the array accordingly:
$tree->tree['foo']['qux'] = 'whatever';
With the decisive hint from gabriel.sobrinho#gmail.com at www.php.net
The method rebuildTree does the job. But it doesn't just change a value in $this->tree. It creates a new array.
Class Tree {
private $tree = array(
'lorem' => array(
'ipsum' => array(
'dolor' => array(
'sit' => 'old value'
)
),
'amet' => array(
'consetetur' => array(
'sadipscing' => 'another value'
)
)
)
);
public function setValue(array $path_and_value) {
$this->tree = $this->rebuildTree($this->tree, $path_and_value);
}
public function getTree() {
return $this->tree;
}
private function rebuildTree(array $arr, array $path_and_value) {
foreach($path_and_value AS $key => $value) {
if(
is_array($value)
&& isset($arr[$key])
) {
$arr[$key] = $this->rebuildTree($arr[$key], $value);
}
else {
$arr[$key] = $value;
}
}
return $arr;
}
}
$Tree = new Tree();
$path_and_value = array(
'lorem' => array(
'ipsum' => array(
'dolor' => array(
'sit' => 'new value'
)
)
)
);
$Tree->setValue($path_and_value);
print_r($Tree->getTree());
// ['lorem' => ['ipsum' => ['dolor' => ['sit' => 'new value']]]]

Iterating over multidimensional array in PHP

Over the past few days, I've been thinking about how to deal with iterating over keys in a multidimensional array, and I just cannot figure it out. The problem is, I don't know how deep the array might be - I want my code to be able to handle arrays of any depth.
The array itself comes from Advanced Custom Fields, but that's not too important. I need to iterate over the array, run a function on every array key which starts with field_ (to convert it from field_* to its proper name like post_title or something), and reconstruct the array with the same structure and values (although the order is not important). The array looks like this:
array (size=12)
'field_5b23d04fef8a6' => string '' (length=0)
'field_5b23d04fefa99' => string '' (length=0)
'field_5b23d04fefe85' => string '' (length=0)
'field_5b23d04ff0077' => string '' (length=0)
'field_5b23d04ff026c' => string '' (length=0)
'field_5b23d0bdb3c1a' => string 'Version 1' (length=9)
'field_5b23d0f48538b' => string '' (length=0)
'field_5b23d0f485772' => string '' (length=0)
'field_5b23d0d52be2d' => string '' (length=0)
'field_5b5ed10a6a7bc' => string '' (length=0)
'field_5b5ed10a6bcf5' =>
array (size=1)
0 =>
array (size=1)
'field_5b5ed10acd264' =>
array (size=1)
0 =>
array (size=6)
'field_5b5ed10b0c9ca' => string '0' (length=1)
'field_5b5ed10b0cce2' => string 'TEST1234' (length=8)
'field_5b5ed10b0d0fd' => string 'Download title' (length=14)
'field_5b5ed10b0d4e2' => string 'EN' (length=2)
'field_5b5ed10b0d72e' => string 'A00' (length=3)
'field_5b5ed10b0df27' => string '887' (length=3)
'field_5b23d088500a4' => string '' (length=0)
What would be the best way to handle this? I've looked at recursive functions and ResursiveArrayIterator already, but none of the examples I found were close enough to let me figure out what I need.
You can recursively call the same function if it finds a nested array like this:
$input = array(
'field_5b23d04fef8a6' => '',
'field_5b23d04fefa99' => '',
'field_5b23d04fefe85' => '',
'field_5b23d04ff0077' => '',
'field_5b23d04ff026c' => '',
'field_5b23d0bdb3c1a' => 'Version 1',
'field_5b23d0f48538b' => '',
'field_5b23d0f485772' => '',
'field_5b23d0d52be2d' => '',
'field_5b5ed10a6a7bc' => '',
'field_5b5ed10a6bcf5' => array(
array(
'field_5b5ed10acd264' => array(
array(
'field_5b5ed10b0c9ca' => '0',
'field_5b5ed10b0cce2' => 'TEST1234',
'field_5b5ed10b0d0fd' => 'Download title',
'field_5b5ed10b0d4e2' => 'EN',
'field_5b5ed10b0d72e' => 'A00',
'field_5b5ed10b0df27' => '887',
),
),
),
),
'field_5b23d088500a4' => '',
);
// recursively re-key array
function dostuff($input){
// always refer to self, even if you rename the function
$thisfunction = __function__;
$output = array();
foreach($input as $key => $value){
// change key
$newkey = (is_string($key) ? preg_replace('/^field_/', 'post_title_', $key) : $key);
// iterate on arrays
if(is_array($value)){
$value = $thisfunction($value);
}
$output[$newkey] = $value;
}
return $output;
}
var_dump(dostuff($input));
So I was looking at this and to my knowledge there is no wrapper function for recursion with callbacks, so here it is:
// general function for recursively doing something
// $input -> array() / the array you wan to process
// $valuefunction -> callable | null / function to run on all values *
// $keyfunction -> callable | null / function to run on all keys *
// * at least one has to defined or there is nothing to do
// callable has two inputs
// $input -> current branch
// $depth -> (int) how deep in the structure are we
// i.e: recursion($some_array, function($branch, $depth){something..}, 'trim');
function recursion($input, $valuefunction = false, $keyfunction = false){
if(!is_array($input)){
trigger_error('Input is '.gettype($input).'. Array expected', E_USER_ERROR);
return null;
}
if(!is_callable($valuefunction)){$valuefunction = false;}
if(!is_callable($keyfunction)){$keyfunction = false;}
if(!$valuefunction && !$keyfunction){
trigger_error('Input is unchanged!', E_USER_WARNING);
return $input;
}
// use recursion internally, so I can pass stuff by reference
// and do the above checks only once.
$recurse = function(&$branch, $depth = 0) use (&$recurse, &$valuefunction, &$keyfunction){
$output = array();
foreach($branch as $key => $value){
$key = $keyfunction ? $keyfunction($key, $depth) : $key;
$output[$key] = (is_array($value) ?
$recurse($value, $depth + 1) :
($valuefunction ?
$valuefunction($value, $depth) :
$value
)
);
}
return $output;
};
return $recurse($input);
}
$valuefunction = function($value, $depth){
return is_string($value) ? $depth.'_'.$value : $value;
};
function keyfunction($key){
return is_string($key) ? preg_replace('/^field_/', 'post_title_', $key) : $key;
}
var_dump(recursion($input, $valuefunction, 'keyfunction'));
Or for your example:
var_dump(recursion($input, 0, function($key){
return is_string($key) ? preg_replace('/^field_/', 'post_title_', $key) : $key;
}));
You could do something like this:
$arr = [
'a',
'b',
'c',
[
'd',
'e',
'f',
[
'g',
'h',
'i',
],
],
];
class MyIterator
{
public function iterate( $array )
{
foreach ( $array as $a ) {
if ( is_array( $a ) ) {
$this->iterate($a);
} else {
echo $a;
}
}
}
}
$iterator = new MyIterator();
$iterator->iterate($arr);
It prints this:
abcdefghi
You can iterate over array recursively like this
function recursiveWalk($array, callable $x)
{
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result[$key] = recursiveWalk($value, $x);
} else {
$result[$key] = $x($value);
}
}
return $result;
}
Here example:
$array = [
"aaa" => 1,
"sub1" => [
"xxx" => 2,
"sub2" => [
"yyy" => 3,
"ttt" => 4
]
]
];
print_r(recursiveWalk($array, function ($x) {
return $x + 1;
}));
Array
(
[aaa] => 2
[sub1] => Array
(
[xxx] => 3
[sub2] => Array
(
[yyy] => 4
[ttt] => 5
)
)
)

Search values on multidimensional array then display the result

I'm trying to retrieve the values on multidimensional arrays using a search like function.
$rows = array(
array(
'Name'=>'City of God',
'Year'=>'2002',
'Rating'=>'10'
),
array(
'Name'=>'The Great Escape',
'Year'=>'1963',
'Rating'=>'9'
),
array(
'Name'=>'Dune',
'Year'=>'1984',
'Rating'=>'6'
),
array(
'Name'=>'Superbabies: Baby Geniuses 2',
'Year'=>'2004',
'Rating'=>'1'
)
);
So for example, if you want to search the array with a value of Name with 'City of God' and Year with '1963' the expected output should be like this
$rows = array(
array(
'Name'=>'City of God',
'Year'=>'2002',
'Rating'=>'10'
),
array(
'Name'=>'The Great Escape',
'Year'=>'1963',
'Rating'=>'9'
),
);
Currently, the function I am using is this
function multiSearch(array $array, array $pairs)
{
$found = array();
foreach ($array as $aKey => $aVal) {
$coincidences = 0;
foreach ($pairs as $pKey => $pVal) {
if (array_key_exists($pKey, $aVal) && $aVal[$pKey] == $pVal) {
$coincidences++;
}
}
if ($coincidences == count($pairs)) {
$found[$aKey] = $aVal;
}
}
return $found;
}
However, using this function only capable of retrieving data of the same array key. So for example if I search the value of Name of 'City of God'
$x = multiSearch($rows, array('Name' => 'City of God')
This will display the correct output like this
$rows = array(
array(
'Name'=>'City of God',
'Year'=>'2002',
'Rating'=>'10'
),
);
Unfortunately, if you try to use, 'Name' => 'City of God' and 'Year' => '1963'
It will not display any output. I'm stuck on figuring out on displaying the correct output, any ideas would be appreciated
Try this :
$datas = array(
array(
'Name'=>'City of God',
'Year'=>'2002',
'Rating'=>'10'
),
array(
'Name'=>'The Great Escape',
'Year'=>'1963',
'Rating'=>'9'
),
array(
'Name'=>'Dune',
'Year'=>'1984',
'Rating'=>'6'
),
array(
'Name'=>'Superbabies: Baby Geniuses 2',
'Year'=>'2004',
'Rating'=>'1'
)
);
$search = array(
'Name' => 'Dune',
'Year' => '2004'
);
$output = array();
foreach ($datas as $key1 => $row) {
foreach ($row as $key2 => $value) {
if($search[$key2] == $value) {
// if(stristr($value, $search[$key2]) !== FALSE) { // if you want to search
$output[] = $datas[$key1];
break;
}
}
}
echo "<pre>"; print_r($output); exit;
Output:
Array
(
[0] => Array
(
[Name] => Dune
[Year] => 1984
[Rating] => 6
)
[1] => Array
(
[Name] => Superbabies: Baby Geniuses 2
[Year] => 2004
[Rating] => 1
)
)
If you need OR, as you mentioned in your question, you then need to see if at least one coincidence happened:
if ($coincidences > 0) {
$found[$aKey] = $aVal;
}
This way, both entries:
$rows = array(
array(
'Name'=>'City of God',
'Year'=>'2002',
'Rating'=>'10'
),
array(
'Name'=>'The Great Escape',
'Year'=>'1963',
'Rating'=>'9'
),
);
will be matched.
Modify the search function to this - works for both the cases mentioned:
function multiSearch($rows, $value) {
$newArr = array();
foreach ($rows as $row) {
foreach ($value as $key => $val) {
if (isset($row[$key]) && $row[$key] == $val) {
$newArr[] = $row;
}
}
}
return $newArr;
}
Calling with one or two values produces the required output -
$x = multiSearch($rows, array('Name' => 'City of God'));
$x = multiSearch($rows, array('Name' => 'City of God', 'Year' => '1963'));
//returns $rows containing this pair
Take a look on my suggestion
function search ($storage, $criteria)
{
return array_filter(
$storage,
function ($row) use ($criteria) {
foreach ($criteria as $key => $value) {
if ($row[$key] === $value) {
return true;
}
}
}
);
}
$result = search($datas, $search);
/**
* $result = [
* 2 => [
* 'Name' => 'Dune'
* 'Year' => '1984'
* 'Rating' => '6'
* ],
* 3 => [
* 'Name' => 'Superbabies: Baby Geniuses 2'
* 'Year' => '2004'
* 'Rating' => '1'
* ]
* ];
*/
what about a recursive call?
function recursiveInArray ($needle, $array) {
$status = false;
foreach($array as $value) {
if(is_array($value)) {
recursiveInArray($needle, $value);
} else {
$status = ($needle == $value);
}
}
return $status;
}
i know its abstract, but should help you working with multi dimensional searching

PHP - find value comparing two multi-dimensional arrays

I've here two multi-dimensional arrays.
How would you do to get the image_to_get value in the $b array thanks to the $a array ?
$a = array(
'thumbs' => array(
'0' => array(
'thumb1a' => array(
'0' => array(
'thumb1' => ""
)
)
)
)
);
$b = array(
'thumbs' => array(
'0' => array(
'thumb1a' => array(
'0' => array(
'thumb1' => "image_to_get"
)
),
'thumb2' => 'image2',
'thumb3' => 'image3',
'thumb4' => 'image4',
'thumb5' => 'image5',
)
)
);
You can try with:
function getAPath($array) {
if (empty($array)) {
return array();
}
$key = key($array);
return array_merge(array($key), getAPath($array[$key]));
}
function getBValue($array, $path) {
$key = array_shift($path);
if (is_null($key) || empty($array)) {
return $array;
}
return getBValue($array[$key], $path);
}
$aPath = getAPath($a);
$bValue = getBValue($b, $aPath);
var_dump($bValue);
First function getAPath flatterns your $a array into:
array (size=5)
0 => string 'thumbs' (length=6)
1 => int 0
2 => string 'thumb1a' (length=7)
3 => int 0
4 => string 'thumb1' (length=6)
Second function getBValue walks through the $b array using $aPath.
Below lovely one-liners ;-)
function getAPath($array) {
return empty($array) ? array() : array_merge(array($key = key($array)), getAPath($array[$key]));
}
function getBValue($array, $path) {
return (is_null($key = array_shift($path)) || empty($array)) ? $array : getBValue($array[$key], $path);
}

Convert array keys to a string in PHP

Example Array:
$array = array([key1] =>
array([key11] =>
array([key111] => 'value111',
[key112] => 'value112',
[key113] => 'value113',
[key114] => array(A,B,C,D),
),
),
);
I need an output as below array:
array([key1/key11/key111] => 'value111',
[key1/key11/key112] => 'value112',
[key1/key11/key113] => 'value113',
[key1/key11/key114] => 'A,B,C,D' );
and i have tried using this function,
function listArrayRecursive($someArray, &$outputArray, $separator = "/") {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($someArray), RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $k => $v) {
if (!$iterator->hasChildren()) {
for ($p = array(), $i = 0, $z = $iterator->getDepth(); $i <= $z; $i++) {
$p[] = $iterator->getSubIterator($i)->key();
}
$path = implode($separator, $p);
$outputArray[] = $path;
}
}
}
$outputArray = array();
listArrayRecursive($array, $outputArray);
I cant able to find how to achieve this by using the above function for "key1/key11/key114" getting value as i expected. Please help me on this.
Input:
$array = array(
'key1' => array(
'key11' => array(
'key111' => 'value111',
'key112' => 'value112',
'key113' => 'value113',
'key114' => array('A','B','C','D'),
),
'key12' => array(
'key121' => 'value121',
'key122' => 'value122',
'key123' => 'value123',
'key124' => array('A','B','C','D'),
),
),
'key2' => array(
'key21' => array(
'key211' => 'value111',
'key212' => 'value112',
'key213' => 'value113',
'key214' => array('A','B','C','D'),
),
),
);
Script:
function remap_keys($input, $max_depth, $separator = '/', /* reserved */ $keychain = array(), /* reserved */ &$output = array())
{
foreach ($input as $key => $element)
{
$element_keychain = array_merge($keychain, (array)$key);
if (($max_depth > 1) && is_array($element))
remap_keys($element, $max_depth -1, $separator, $element_keychain, $output);
else
$output[implode($separator, $element_keychain)] = implode(',', (array)$element);
}
return $output;
}
$array = remap_keys($array, 3);
print_r($array);
Output:
Array
(
[key1/key11/key111] => value111
[key1/key11/key112] => value112
[key1/key11/key113] => value113
[key1/key11/key114] => A,B,C,D
[key1/key12/key121] => value121
[key1/key12/key122] => value122
[key1/key12/key123] => value123
[key1/key12/key124] => A,B,C,D
[key2/key21/key211] => value111
[key2/key21/key212] => value112
[key2/key21/key213] => value113
[key2/key21/key214] => A,B,C,D
)
http://ideone.com/pqH45h
$array = array('key1' =>
array('key11' =>
array('key111' => 'value111',
'key112' => 'value112',
'key113' => 'value113',
'key114' => array(A,B,C,D),
),
),
);
function implode_arr_keys($array, $output_arr = array(), $cur_key = FALSE) {
foreach($array as $key => $value) {
if(is_array($value)) {
return implode_arr_keys($value, $output_arr, ($cur_key == FALSE ? $key : $cur_key.'/'.$key));
} else {
if(!is_numeric($key))
$output_arr[$cur_key.'/'.$key] = $value;
else
$output_arr[$cur_key] = $array;
}
}
return $output_arr;
}
print_r($array);
print_r(implode_arr_keys($array));

Categories