i am new on stackoverflow. I have a question. How do this code more appropiate for value replacement in nested arrays?
For example, i have this array obtained from json file:
$json_file = "template.json";
$json = file_get_contents($json_file);
$array = json_decode($json, true);
Now, i obtain the array $res that contain certain searched value:
function array_recursive_search_key_map($needle, $haystack) {
foreach($haystack as $first_level_key=>$value) {
if ($needle === $value) {
return array($first_level_key);
} elseif (is_array($value)) {
$callback = array_recursive_search_key_map($needle, $value);
if ($callback) {
return array_merge(array($first_level_key), $callback);
}
}
}
return false;
}
$res = array_recursive_search_key_map("id|logo", $array);
Doing this, $res contains the position of the searched value in nested $array:
$res = ["content",0,"elements",0,"elements",0,"elements",0,"elements",0,"settings","_attributes"]
I want to replace progamatically the value in:
$array["content"][0]["elements"][0]["elements"][0]["elements"][0]["elements"][0]["settings"]["_attributes"] = "some new value";
I was created a new function but i think are not very practically:
function recursive_replacement($keymap, $newvalue)
{
$size = sizeof($keymap);
switch ($size)
{
case 1:
$array[$keymap[0]] = $newvalue;
break;
case 2:
$array[$keymap[0]][$keymap[1]] = $newvalue;
break;
case 3:
$array[$keymap[0]][$keymap[1]][$keymap[2]] = $newvalue;
break;
case 4:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]] = $newvalue;
break;
case 5:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]][$keymap[4]] = $newvalue;
break;
case 6:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]][$keymap[4]][$keymap[5]] = $newvalue;
break;
case 7:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]][$keymap[4]][$keymap[5]][$keymap[6]] = $newvalue;
break;
case 8:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]][$keymap[4]][$keymap[5]][$keymap[6]][$keymap[7]] = $newvalue;
break;
case 9:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]][$keymap[4]][$keymap[5]][$keymap[6]][$keymap[7]][$keymap[8]] = $newvalue;
break;
case 10:
$array[$keymap[0]][$keymap[1]][$keymap[2]][$keymap[3]][$keymap[4]][$keymap[5]][$keymap[6]][$keymap[7]][$keymap[8]][$keymap[9]] = $newvalue;
break;
}
return $array;
}
And the use way:
$new_array = recursive_replacement($res, "some new value");
Any suggestion would be appreciated. Thanks!
At the very first step we need a notation to address an item in an array, i like the dot notation
$array = [
'item_1' => [
'item_2' => 1,
'item_3' => [
'item_4',
'item_5'
]
]
];
For example 'item_1.item_3.0' instead of $array['item_1']['item_3'][0]
Now we need to iterate on our address key and find destination element
function replaceArrayItem($searchKey, $replacement, $array) {
$itemAddressBlocks = explode('.', $searchKey); // convert to array so we can iterate on address
$replacedArray = $array;
$temp = &$replacedArray; // pass by reference
foreach($itemAddressBlocks as $depth => $index) {
if( $depth < sizeof($itemAddressBlocks) - 1 ) { // rolling in the deep :)
$temp = &$temp[$index];
continue;
}
$temp[$index] = $replacement; // last step and replacement
}
return $replacedArray;
}
Now lets test our new function
/**
Array
(
[item_1] => ***replacedItem***
)
**/
print_r( replaceArrayItem('item_1', 'replacedItem', $array) );
/**
Array
(
[item_1] => Array
(
[item_2] => ***replacedItem***
[item_3] => Array
(
[0] => item_4
[1] => item_5
)
)
)
**/
print_r( replaceArrayItem('item_1.item_2', 'replacedItem', $array) );
/**
Array
(
[item_1] => Array
(
[item_2] => 1
[item_3] => Array
(
[0] => item_4
[1] => ***replacedItem***
)
)
)
**/
print_r( replaceArrayItem('item_1.item_3.1', 'replacedItem', $array) );
Now You can progamatically
$itemAddress = 'content.0';
for($i =0; $i < 4; $i++) {
$itemAddress .= '.elements.0';
}
$itemAddress .= '.settings._attributes';
replaceArrayItem($itemAddress, $yourValue, $array);
Related
I have an array that looks like this:
$array = array (
[level_1] => array (
[level_2] => array (
[level_3] => something
)
),
[level_12] => array (
[level_2] => somethingelse
),
[level_13] => array (
[level_22] => array (
[level_3] => something
)
),
);
The keys or values aren't always unique but the branches are.
And I have a string that looks like this:
$string = 'level_1-level_2-level_3';
Those are the keys for a branch.
And I need to somehow get the value from the array based on that string?
Like this:
$string_array = explode('-', $string);
$array[$string_array[0]][$string_array[1]][$string_array[2]] // something
But since the depth can be different this is not a viable solution...
Try this simple example, no need for a recursive function:
function get_item( $path, $array )
{
$paths = explode( '-', $path );
$result = $array;
foreach ( $paths as $path) {
isset( $result[$path] ) ? $result = $result[$path] : $result = false;
}
return $result;
}
$path = 'level_1-level_2-level_3';
echo get_item( $path, $array );
Try this:
$array = array (
'level_1' => array (
'level_2' => array (
'level_3' => 'something'
)
),
'level_12' => array (
'level_2' => 'somethingelse'
),
'level_13' => array (
'level_22' => array (
'level_3' => 'something'
)
),
);
$string = 'level_1-level_2-level_3';
$keys = explode('-', $string);
echo getItemIterative($keys, $array);
echo "\n";
echo getItemRecursive($keys, $array);
function getItemIterative($keys, $array)
{
$value = null;
foreach ($keys as $key) {
if ($value == null) {
$value = $array[$key];
}
if (is_array($value) && array_key_exists($key, $value)) {
$value = $value[$key];
}
}
return $value;
}
function getItemRecursive($keys, $array)
{
$key = array_shift($keys);
$value = $array[$key];
if (empty($keys)) {
return $value;
} else {
return getItemRecursive($keys, $value);
}
}
Make a $result variable which initially points to the root of the array, and loop through the levels of your $string_array 'til $result points at the leaf you were looking for.
// stuff you already have:
$array = array(...); // your big array
$string = 'level_1-level_2-level_3';
$string_array = explode('-', $string);
// new stuff:
$result = $array;
foreach ($string_array as $level) {
$result = $result[$level];
}
echo $result; // 'something'
Working example: Ideone
I want to dissect an array like this:
[
"ID",
"UUID",
"pushNotifications.sent",
"campaigns.boundDate",
"campaigns.endDate",
"campaigns.pushMessages.sentDate",
"pushNotifications.tapped"
]
To a format like this:
{
"ID" : 1,
"UUID" : 1,
"pushNotifications" :
{
"sent" : 1,
"tapped" : 1
},
"campaigns" :
{
"boundDate" : 1,
"endDate" : 1,
"pushMessages" :
{
"endDate" : 1
}
}
}
It would be great if I could just set a value on an associative array in a keypath-like manner:
//To achieve this:
$dissected['campaigns']['pushMessages']['sentDate'] = 1;
//By something like this:
$keypath = 'campaigns.pushMessages.sentDate';
$dissected{$keypath} = 1;
How to do this in PHP?
You can use :
$array = [
"ID",
"UUID",
"pushNotifications.sent",
"campaigns.boundDate",
"campaigns.endDate",
"campaigns.pushMessages.sentDate",
"pushNotifications.tapped"
];
// Build Data
$data = array();
foreach($array as $v) {
setValue($data, $v, 1);
}
// Get Value
echo getValue($data, "campaigns.pushMessages.sentDate"); // output 1
Function Used
function setValue(array &$data, $path, $value) {
$temp = &$data;
foreach(explode(".", $path) as $key) {
$temp = &$temp[$key];
}
$temp = $value;
}
function getValue($data, $path) {
$temp = $data;
foreach(explode(".", $path) as $ndx) {
$temp = isset($temp[$ndx]) ? $temp[$ndx] : null;
}
return $temp;
}
function keyset(&$arr, $keypath, $value = NULL)
{
$keys = explode('.', $keypath);
$current = &$arr;
while(count($keys))
{
$key = array_shift($keys);
if(!isset($current[$key]) && count($keys))
{
$current[$key] = array();
}
if(count($keys))
{
$current = &$current[$key];
}
}
$current[$key] = $value;
}
function keyget($arr, $keypath)
{
$keys = explode('.', $keypath);
$current = $arr;
foreach($keys as $key)
{
if(!isset($current[$key]))
{
return NULL;
}
$current = $current[$key];
}
return $current;
}
//Testing code:
$r = array();
header('content-type: text/plain; charset-utf8');
keyset($r, 'this.is.path', 39);
echo keyget($r, 'this.is.path');
var_dump($r);
It's a little rough, I can't guarantee it functions 100%.
Edit: At first you'd be tempted to try to use variable variables, but I've tried that in the past and it doesn't work, so you have to use functions to do it. This works with some limited tests. (And I just added a minor edit to remove an unnecessary array assignment.)
In the meanwhile, I came up with (another) solution:
private function setValueForKeyPath(&$array, $value, $keyPath)
{
$keys = explode(".", $keyPath, 2);
$firstKey = $keys[0];
$remainingKeys = (count($keys) == 2) ? $keys[1] : null;
$isLeaf = ($remainingKeys == null);
if ($isLeaf)
$array[$firstKey] = $value;
else
$this->setValueForKeyPath($array[$firstKey], $value, $remainingKeys);
}
Sorry for the "long" namings, I came from the Objective-C world. :)
So calling this on each keyPath, it actually gives me the output:
fields
Array
(
[0] => ID
[1] => UUID
[2] => pushNotifications.sent
[3] => campaigns.boundDate
[4] => campaigns.endDate
[5] => campaigns.pushMessages.endDate
[6] => pushNotifications.tapped
)
dissectedFields
Array
(
[ID] => 1
[UUID] => 1
[pushNotifications] => Array
(
[sent] => 1
[tapped] => 1
)
[campaigns] => Array
(
[boundDate] => 1
[endDate] => 1
[pushMessages] => Array
(
[endDate] => 1
)
)
)
Here is print_r output of my array:
Array
(
[0] => stdClass Object
(
[itemId] => 560639000019
[name] => Item no1
[code] => 00001
[qty] => 5
[id] => 2
)
[1] => stdClass Object
(
[itemId] => 470639763471
[name] => Second item
[code] => 76347
[qty] => 9
[id] => 4
)
[2] => stdClass Object
(
[itemId] => 56939399632
[name] => Item no 3
[code] => 39963
[qty] => 6
[id] => 7
)
)
How can I find index of object with [id] => 4 in order to remove it from array?
$found = false;
foreach($values as $key => $value) {
if ($value->id == 4) {
$found = true;
break;
}
}
if ($found) unset($values[$key]);
This is considered to be faster then any other solution since we only iterate the array to until we find the object we want to remove.
Note: You should not remove an element of an array while iterating so we do it afterwards here.
foreach($parentObj AS $key=>$element){
if ($element->id == THE_ID_YOU_ARE_LOOKING_FOR){
echo "Gottcha! The index is - ". $key;
}
}
$parentObj is obviously your root array - the one that holds all the others.
We use the foreach loop to iterate over each item and then test it's id property against what ever value you desire. Once we have that - the $key that we are on is the index you are looking for.
use array_search:
$a = new stdClass;
$b = new stdClass;
$a->id = 1;
$b->id = 2;
$arr = array($a, $b);
$index = array_search($b, $arr);
echo $index;
// prints out 1
try this
foreach($array AS $key=>$object){
if($object['id'] == 4){
$key_in_array = $key;
}
}
// chop it from the original array
array_slice($array, $key_in_array, 1);
Another way to achieve the result is to use array_filter.
$array = array(
(object)array('id' => 5),
(object)array('id' => 4),
(object)array('id' => 3)
);
$array = array_filter($array, function($item) {
return $item->id != 4;
});
print_r($array);
Here's my solution. Given, it is a bit hackish, but it will get the job done.
search(array $items, mixed $id[, &$key]);
Returns the item that was found by $id. If you add the variable $key it will give you the key of the item found.
function search($items, $id, &$key = null) {
foreach( $items as $item ) {
if( $item->id == $id ) {
$key = key($item);
return $item;
break;
}
}
return null;
}
Usage
$item = search($items, 4, $key);
unset($items[$key]);
Note: This could be modified to allow a custom key and return multiple items that share the same value.
I've created an example so you can see it in action.
A funny alternative
$getIdUnset = function($id) use ($myArray)
{
foreach($myArray as $key => $obj) {
if ($obj->id == $id) {
return $key;
}
}
return false;
};
if ($unset = $getIdUnset(4)) {
unset($myArray[$unset]);
}
Currently php does not have any supported function for this yet.
So refer to Java's Vector, or jQuery's $.inArray(), it would simply be:
public function indexOf($object, array $elementData) {
$elementCount = count($elementData);
for ($i = 0 ; $i < $elementCount ; $i++){
if ($object == $elementData[$i]) {
return $i;
}
}
return -1;
}
You can save this function as a core function for later.
In my case, this my array as $array
I was confused about this problem of my project, but some answer here helped me.
array(3) {
[0]=> float(-0.12459619130796)
[1]=> float(-0.64018439966448)
[2]=> float(0)
}
Then use if condition to stop looping
foreach($array as $key => $val){
if($key == 0){ //the key is 0
echo $key; //find the key
echo $val; //get the value
}
}
I know, after so many years this could be a useless answer, but why not?
This is my personal implementation of a possible index_of using the same code as other answers but let the programmer to choose when and how the check will be done, supporting also complex checks.
if (!function_exists('index_of'))
{
/**
* #param iterable $haystack
* #param callable $callback
* #param mixed|null &$item
* #return false|int|string
*/
function index_of($haystack, $callback, &$item = null)
{
foreach($haystack as $_key => $_item) {
if ($callback($_item, $_key) === true) {
$item = $_item;
return $_key;
}
}
return false;
}
}
foreach( $arr as $k=>&$a) {
if( $a['id'] == 4 )
unset($arr[$k]);
}
Looking into Kohana documentation, i found this really usefull function that they use to get values from a multidimensional array using a dot notation, for example:
$foo = array('bar' => array('color' => 'green', 'size' => 'M'));
$value = path($foo, 'bar.color', NULL , '.');
// $value now is 'green'
Im wondering if there is a way to set the an array value in the same way:
set_value($foo, 'bar.color', 'black');
The only way i found to do that is re-building the array notation ($array['bar']['color']) and then set the value.. using eval.
Any idea to avoid eval?
function set_val(array &$arr, $path,$val)
{
$loc = &$arr;
foreach(explode('.', $path) as $step)
{
$loc = &$loc[$step];
}
return $loc = $val;
}
Sure it's possible.
The code
function set_value(&$root, $compositeKey, $value) {
$keys = explode('.', $compositeKey);
while(count($keys) > 1) {
$key = array_shift($keys);
if(!isset($root[$key])) {
$root[$key] = array();
}
$root = &$root[$key];
}
$key = reset($keys);
$root[$key] = $value;
}
How to use it
$foo = array();
set_value($foo, 'bar.color', 'black');
print_r($foo);
Outputs
Array
(
[bar] => Array
(
[color] => black
)
)
See it in action.
Look at https://gist.github.com/elfet/4713488
$dn = new DotNotation(['bar'=>['baz'=>['foo'=>true]]]);
$value = $dn->get('bar.baz.foo'); // $value == true
$dn->set('bar.baz.foo', false); // ['foo'=>false]
$dn->add('bar.baz', ['boo'=>true]); // ['foo'=>false,'boo'=>true]
That way you can set the following values more than once to the same variable.
You can make these two ways (by static variable and reference variable):
<?php
function static_dot_notation($string, $value)
{
static $return;
$token = strtok($string, '.');
$ref =& $return;
while($token !== false)
{
$ref =& $ref[$token];
$token = strtok('.');
}
$ref = $value;
return $return;
}
$test = static_dot_notation('A.1', 'A ONE');
$test = static_dot_notation('A.2', 'A TWO');
$test = static_dot_notation('B.C1', 'C ONE');
$test = static_dot_notation('B.C2', 'C TWO');
$test = static_dot_notation('B.C.D', 'D ONE');
var_export($test);
/**
array (
'A' =>
array (
1 => 'A ONE',
2 => 'A TWO',
),
'B' =>
array (
'C1' => 'C ONE',
'C2' => 'C TWO',
'C' =>
array (
'D' => 'D ONE',
),
),
*/
function reference_dot_notation($string, $value, &$array)
{
static $return;
$token = strtok($string, '.');
$ref =& $return;
while($token !== false)
{
$ref =& $ref[$token];
$token = strtok('.');
}
$ref = $value;
$array = $return;
}
reference_dot_notation('person.name', 'Wallace', $test2);
reference_dot_notation('person.lastname', 'Maxters', $test2);
var_export($test2);
/**
array (
'person' =>
array (
'name' => 'Wallace',
'lastname' => 'Maxters',
),
)
*/
I created a small class just for this!
http://github.com/projectmeta/Stingray
$stingray = new StingRay();
//To Get value
$stingray->get($array, 'this.that.someother'):
//To Set value
$stingray->get($array, 'this.that.someother', $newValue):
Updated #hair resins' answer to cater for:
When a sub-path already exists, or
When a sub-path is not an array
function set_val(array &$arr, $path,$val)
{
$loc = &$arr;
$path = explode('.', $path);
foreach($path as $step)
{
if ( ! isset($loc[$step]) OR ! is_array($loc[$step]))
$loc = &$loc[$step];
}
return $loc = $val;
}
None of the examples here worked for me, so I came up with a solution using eval() (read about the risks here, but if you don't use user data, it shouldn't be much of an issue). The if-clause in the set-method allows you to push your item onto a new or existing array at that location ($location[] = $item).
class ArrayDot {
public static function get(array &$array, string $path, string $delimiter = '.') {
return eval("return ".self::getLocationCode($array, $path, $delimiter).";");
}
public static function set(array &$array, string $path, $item, string $delimiter = '.') : void {
//if the last character is a delimiter, allow pushing onto a new or existing array
$add = substr($path, -1) == $delimiter ? '[]': '';
eval(self::getLocationCode($array, $path, $delimiter).$add." = \$item;");
}
public static function unset(array &$array, $path, string $delimiter = '.') : void {
if (is_array($path)) {
foreach($path as $part) {
self::unset($array, $part, $delimiter);
}
}
else {
eval('unset('.self::getLocationCode($array, $path, $delimiter).');');
}
}
public static function isSet(array &$array, $path, string $delimiter = '.') : bool {
if (is_array($path)) {
foreach($path as $part) {
if (!self::isSet($array, $part, $delimiter)) {
return false;
}
}
return true;
}
return eval("return isset(".self::getLocationCode($array, $path, $delimiter).");");
}
private static function getLocationCode(array &$array, string $path, string $delimiter) : string {
$path = rtrim($path, $delimiter); //Trim trailing delimiters
$escapedPathParts = array_map(function ($s) { return str_replace('\'', '\\\'', $s); }, explode($delimiter, $path));
return "\$array['".implode("']['", $escapedPathParts)."']";
}
}
Example usage:
echo '<pre>';
$array = [];
ArrayDot::set($array, 'one.two.three.', 'one.two.three.');
ArrayDot::set($array, 'one.two.three.four.', 'one.two.three.four.');
ArrayDot::set($array, 'one.two.three.four.', 'one.two.three.four. again');
ArrayDot::set($array, 'one.two.three.five.', 'one.two.three.five.');
ArrayDot::set($array, 'one.two.three.direct set', 'one.two.three.direct set');
print_r($array);
echo "\n";
echo "one.two.three.direct set: ".print_r(ArrayDot::get($array, 'one.two.three.direct set'), true)."\n";
echo "one.two.three.four: ".print_r(ArrayDot::get($array, 'one.two.three.four'), true)."\n";
Output:
Array
(
[one] => Array
(
[two] => Array
(
[three] => Array
(
[0] => one.two.three.
[four] => Array
(
[0] => one.two.three.four.
[1] => one.two.three.four. again
)
[five] => Array
(
[0] => one.two.three.five.
)
[direct set] => one.two.three.direct set
)
)
)
)
one.two.three.direct set: one.two.three.direct set
one.two.three.four: Array
(
[0] => one.two.three.four.
[1] => one.two.three.four. again
)
I'm trying to parse the following format with PHP:
// This is a comment
{
this is an entry
}
{
this is another entry
}
{
entry
{entry within entry}
{entry within entry}
}
Maybe is just the lack of caffeine, but i can't think of a decent way of getting the contents of the curly braces.
This is quite a common parsing task, basically you need to keep track of the various states you can be in and use a combination of constants and function calls to maintain them.
Here is some rather inelegant code that does just that:
<?php
$input = file_get_contents('input.txt');
define('STATE_CDATA', 0);
define('STATE_COMMENT', 1);
function parseBrace($input, &$i)
{
$parsed = array(
'cdata' => '',
'children' => array()
);
$length = strlen($input);
$state = STATE_CDATA;
for(++$i; $i < $length; ++$i) {
switch($input[$i]) {
case '/':
if ('/' === $input[$i+1]) {
$state = STATE_COMMENT;
++$i;
} if (STATE_CDATA === $state) {
$parsed['cdata'] .= $input[$i];
}
break;
case '{':
if (STATE_CDATA === $state) {
$parsed['children'][] = parseBrace($input, $i);
}
break;
case '}':
if (STATE_CDATA === $state) {
break 2; // for
}
break;
case "\n":
if (STATE_CDATA === $state) {
$parsed['cdata'] .= $input[$i];
}
$state = STATE_CDATA;
break;
default:
if (STATE_CDATA === $state) {
$parsed['cdata'] .= $input[$i];
}
}
}
return $parsed;
}
function parseInput($input)
{
$parsed = array(
'cdata' => '',
'children' => array()
);
$state = STATE_CDATA;
$length = strlen($input);
for($i = 0; $i < $length; ++$i) {
switch($input[$i]) {
case '/':
if ('/' === $input[$i+1]) {
$state = STATE_COMMENT;
++$i;
} if (STATE_CDATA === $state) {
$parsed['cdata'] .= $input[$i];
}
break;
case '{':
if (STATE_CDATA === $state) {
$parsed['children'][] = parseBrace($input, $i);
}
break;
case "\n":
if (STATE_CDATA === $state) {
$parsed['cdata'] .= $input[$i];
}
$state = STATE_CDATA;
break;
default:
if (STATE_CDATA === $state) {
$parsed['cdata'] .= $input[$i];
}
}
}
return $parsed;
}
print_r(parseInput($input));
This produces the following output:
Array
(
[cdata] =>
[children] => Array
(
[0] => Array
(
[cdata] =>
this is an entry
[children] => Array
(
)
)
[1] => Array
(
[cdata] =>
this is another entry
[children] => Array
(
)
)
[2] => Array
(
[cdata] =>
entry
[children] => Array
(
[0] => Array
(
[cdata] => entry within entry
[children] => Array
(
)
)
[1] => Array
(
[cdata] => entry within entry
[children] => Array
(
)
)
)
)
)
)
You'll probably want to clean up all the whitespace but some well placed trim's will sort that for you.
This may not be the best solution for large amount of content, but it works.
<?php
$text = "I am out of the brackets {hi i am in the brackets} Back out { Back in}";
print $text . '<hr />';
$tmp = explode("{",$text);
$tmp2 = array();
$wantedText = array();
for($i = 0; $i < count($tmp); $i++){
if(stristr($tmp[$i],"}")){
$tmp2 = explode("}",$tmp[$i]);
array_push($wantedText,$tmp2[0]);
}
}
print_r($wantedText);
?>
Results:
Array ( [0] => hi i am in the brackets [1] => Back in )