I have a small problem with my recursive function, and it is that FQDN is not being created correctly.
public function testBuildReverseSchemaNames() {
$input = [
'propertyZeroSchema' => 'value_0',
'communicationPreferences' => [
'grv3_newsletter_bi' => true
],
'someContainer' => [
'propertyOneSchema' => 'value_1'
],
'propertyThreeSchema' => 'value_3'
];
$expected = [
'propertyZeroSchema' => 'value_0',
'communicationPreferences.grv3_newsletter_bi' => true,
'someContainer.propertyOneSchema' => 'value_1',
'propertyThreeSchema' => 'value_3'
];
$output = [];
$this->buildReverseSchemaNames($input, $output);
$this->assertEquals($expected, $output);
}
public function buildReverseSchemaNames($data, &$output, $currentFqdn = '') {
foreach ($data as $key => $value) {
if (is_array($value)) {
$currentFqdn .= $key.'.';
$this->buildReverseSchemaNames($value, $output, $currentFqdn);
} else {
$currentFqdn .= $key;
$output[$currentFqdn] = $value;
}
}
}
But the output is like this:
Array (
'propertyZeroSchema' => 'value_0'
'propertyZeroSchemacommunicationPreferences.grv3_newsletter_bi' => true
'propertyZeroSchemacommunicationPreferences.someContainer.propertyOneSchema' => 'value_1'
'propertyZeroSchemacommunicationPreferences.someContainer.propertyThreeSchema' => 'value_3'
)
Well, I was able to find the answer by myself:
Edit: After I thought about doing it cleaner, #jh1711 just answered this. If you repost it I will accept your answer.
public function buildReverseSchemaNames($data, &$output, $currentFqdn = '') {
foreach ($data as $key => $value) {
if (is_array($value)) {
$this->buildReverseSchemaNames($value, $output, $currentFqdn . $key.'.');
} else {
$output[$currentFqdn.$key] = $value;
}
}
}
You should not modify $currentFqdn inside your function. Otherwise your changes will affect later iteration of the foreach loop. It should work, if you modify your code like this:
public function buildReverseSchemaNames($data, &$output, $currentFqdn = '') {
foreach ($data as $key => $value) {
if (is_array($value)) {
$this->buildReverseSchemaNames($value, $output, $currentFqdn.$key.'.');
} else {
$output[$currentFqdn.$key] = $value;
}
}
}
Related
I have a multidimensional array like this:
array (
level1 => array ( level1.1,
level1.2)
level2 => array ( level2.1,
level2.2 => array( level2.2.1 => 'foo',
level2.2.2 => 'bar',
level2.2.3 => 'test')
)
)
As a result I want an array of strings like this
array ("level1/level1.1",
"level1/level1.2",
"level2/level2.1",
"level2/level2.2/level2.2.1",
"level2/level2.2/level2.2.2",
"level2/level2.2/level2.2.3")
Here is the code I tried
function displayArrayRecursively($array, string $path) : array {
if($path == "")
$result_array = array();
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->displayArrayRecursively($value, $path . $key . '/');
} else {
$result_array[] = $path . $key; }
}
return $result_array;
}
Any idea how I can achieve this. I could use a reference array to populate, but I want to solve it with return values.
$array = [
'level1' => [
'level1.1',
'level1.2'
],
'level2' => [
'level2.1',
'level2.2' => [
'level2.2.1' => 'foo',
'level2.2.2' => 'bar',
'level2.2.3' => 'test'
]
]
];
function arrayParser(array $array, ?string $path=null) {
$res = [];
foreach($array as $key => $value) {
if(is_array($value)) {
$res[] = arrayParser($value, ($path ? $path.'/' : $path).$key);
}
else {
$res[] = $path.'/'.(!is_numeric($key) ? $key : $value);
}
}
return flatten($res);
}
function flatten(array $array) {
$return = array();
array_walk_recursive($array, function($a) use (&$return) { $return[] = $a; });
return $return;
}
$res = arrayParser($array); // result
Please help me.. I spent a lot of time on it and I think I ran out of ideas.
I have that code for function array_to_xml:
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
if (!empty($value)) {
if( is_array($value)) {
if (!empty($value["#attributes"])) {
$subnode = $xml_data->addChild($key, $value["#value"]);
foreach ($value["#attributes"] as $key1 => $val1) {
$subnode->addAttribute($key1, $val1);
}
} else if ($key == "#value") {
foreach ($value as $attr => $attrVal) {
$subnode = $xml_data->addChild("$attr", $attrVal);
array_to_xml($attrVal, $subnode);
}
} else {
if (!empty($value) and $key != "kontakt") {
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
} else if (!empty($value) and $key == "kontakt") {
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
};
}
} else {
$xml_data->addChild("$key",$value);
}
}
}
}
When it hits the "contact" attribute, it creates the following structure:
<kontakt>
<1 typ="email">abc#abc.pl</1>
<2 typ="telefon">3123453141</2>
</kontakt>
How should the function be changed so that its result would be the structure shown below?
<kontakt typ="email">abc#abc.pl</kontakt>
<kontakt typ="telefon">3123453141</kontakt>
The array:
"kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
],
That's the only thing I'm missing for happiness ;)
something like that (i´ve rewritten your function):
<?php
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
foreach($value as $innerkey => $innervalue) {
$v = $innervalue['#value'];
$t = "";
if(isset($innervalue['#attributes']['typ'])) {
$t = $innervalue['#attributes']['typ'];
}
$kontakt = $xml_data->addChild('kontakt', $v);
$kontakt->addAttribute('typ', $t);
}
}
}
$data = array("kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
]
);
$xml_data = new SimpleXMLElement('<test></test>') ;
array_to_xml($data,$xml_data);
echo $xml_data->asXML();
results in
<?xml version="1.0"?>
<test><kontakt typ="email">abc#abc.pl</kontakt><kontakt typ="telefon">3123453141</kontakt></test>
playground: https://3v4l.org/Qo9uaU#v7.0.33
<?php
//the array
$data = array(
"kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
]);
//call to function
$xml = array_to_xml($data);
show($xml);
//function to convert the array to xml
function array_to_xml( $data) {
$xml = '';
foreach( $data as $key => $value ) {
foreach($value as $key1 => $value1){
$xml .= '<kontakt typ="'.$value1['#attributes']['typ'].'">';
$xml .= $value1['#value'].'</kontakt>'.PHP_EOL;
}
}
return $xml;
}
//function to show it in xml form
function show($data){
echo '<pre>';
echo nl2br(str_replace('<', '<', $data));
echo '</pre>';
}
?>
May this will help you. You can configure in case of multiple array. The above function will work in case of the provided string.
Before writing the recursive function it can help to line-out which cases there are in the data. I found three, and all what is needed to identify them is the key that is always string (for the recursive traversal):
key is #value -> the elements' node-value
key is #attributes -> the elements' attributes key/value map
key is the name of child elements -> list of children to add
Only the third case traverses into child nodes.
function array_to_xml(array $array, SimpleXMLElement $xml): void
{
foreach ($array as $key => $value) {
if ($key === '#value') {
$xml[0] = $value;
} elseif ($key === '#attributes') {
foreach ($value as $name => $item) {
$xml->addAttribute($name, $item);
}
} else {
foreach ($value as $item) {
array_to_xml($item, $xml->addChild($key));
}
}
}
}
It is not only far less code, the main difference is that the function operates on the element itself; it doesn't fetch the $key only to add it later, in the else case it adds it directly and passes the new child with the array.
The recursion is not scattered at mutliple places, but at a single one.
SimpleXMLElement itself can also be used to give each node actual method(s) and these automatically propagate over the whole document. Here an ad-hoc example:
(new class ('<test/>') extends SimpleXMLElement {
public function add(array $array): self
{
foreach ($array as $key => $value) match ($key) {
'#value' => $this[0] = $value,
'#attributes' => array_map($this->addAttribute(...), array_keys($value), $value),
default => array_map(fn ($child) => $this->addChild($key)?->add($child), $value),
};
return $this;
}
})->add($array)->asXML('php://output');
// <?xml version="1.0"?>
// <test><kontakt typ="email">abc#abc.pl</kontakt><kontakt typ="telefon">3123453141</kontakt></test>
The result is the same, one benefit could the that you don't need to think that much passing $xml around.
I have an array:
$arr = [
['id'=>1, 'name'=>'Peter', 'age'=>28],
['id'=>1, 'name'=>'David', 'age'=>28],
['id'=>2, 'name'=>'John', 'age'=>34],
['id'=>3, 'name'=>'Smith', 'age'=>36],
['id'=>3, 'name'=>'Smith', 'age'=>28],
];
I want filter it by column 'id', and merge the different values. I need output:
$arr = [
['id'=>1, 'name'=>'Peter,David', 'age'=>28],
['id'=>2, 'name'=>'John', 'age'=>34],
['id'=>3, 'name'=>'Smith', 'age'=>'36,28']
];
Is there any PHP function (such as: array_filter, array_walk, array_map,...) to do that, without looping code as below?
function combine_row($arr){
$last = null;
foreach ($arr as $key => $a){
if ($a['id']==$last['id']){
foreach ($a as $k=>$v) {
if($v!==$last[$k]) {
$arr[$key][$k].=','.$last[$k];
}
}
unset($arr[$key-1]);
}
$last = $a;
}
return $arr;
}
Thanks for helping me!
Here is a solution that uses a more functional style. Basically just a lot of the built-in array_* functions. This will ensure less side-effects, making your code less error prone.
The code first finds all unique _id_s. It then filters through the data and joins it in the end.
$result = array_map(
function ($id) use ($arr) {
return array_map(function ($data) {
if (is_array($data)) return join(",", array_unique($data));
return $data;
}, array_reduce(
array_filter($arr, function ($item) use ($id) { return $id === $item["id"]; }),
function ($result, $set) { return $result = array_filter(array_merge_recursive($result, $set)); }, [ ]
)
);
},
array_unique(array_column($arr, "id"))
);
print_r($result);
Output:
Array
(
[0] => Array
(
[id] => 1
[name] => Peter,David
[age] => 28
)
[2] => Array
(
[id] => 2
[name] => John
[age] => 34
)
[3] => Array
(
[id] => 3
[name] => Smith
[age] => 36,28
)
)
Using a chainable apigithub you can achieve much more readable code:
$result = arr($arr)
->column("id")
->unique()
->map(
function ($id) use ($arr) {
return arr($arr)
->filter(function ($item) use ($id) { return $id === $item["id"]; })
->reduce(function ($result, $set) { return array_filter(array_merge_recursive($result, $set)); }, [])
->map(function ($data) {
if (is_array($data)) return arr($data)->unique()->join(",")->toArray();
return $data;
})->toArray();
}
)->toArray();
$result = [];
foreach ($arr as $person) {
foreach ($person as $key => $value) {
if ($key == 'id') {
$result[$person['id']][$key] = $value;
} else {
$result[$person['id']][$key][] = $value;
}
}
}
The magic really is in $result[$person['id']], which groups all items with the same id into the same result array very naturally. This also creates arrays for multiple values instead of merely concatenating the strings, which is a much saner data structure to work with. If you only want unique values, throw in an array_unique there. If you actually need to join the items with commas, use join(',', ..) at the end somewhere.
Check this below code,
function getMergerArray($arr) {
$newArr = array();
foreach ($arr as $key => $value) {
$id = $value['id'];
$name = $value['name'];
$age = $value['age'];
if (array_key_exists($id, $newArr)) {
if (!in_array($name, $newArr[$id]['name'])) {
$newArr[$id]['name'][] = $name;
}
if (!in_array($age, $newArr[$id]['age'])) {
$newArr[$id]['age'][] = $age;
}
continue;
}
$newArr[$id]['name'][] = $name;
$newArr[$id]['age'][] = $age;
}
foreach ($newArr as $key => $value) {
$newArr[$key] = array(
'id' => $key,
'name' => implode(', ', $value['name']),
'age' => implode(', ', $value['age']),
);
}
// echo '<pre>';
// print_r($newArr);
// echo '</pre>';
return $newArr;
}
Try this
function merge_multidimensional($arr) {
$out = array();
foreach ($arr as $key => $value){
$out[] = (object)array_merge((array)$arr2[$key], (array)$value);
}
return $out;
}
I've realized I need to stop banging my head and ask for help...
I have the following array:
$permissionTypes = array(
'system' => array(
'view' => 'View system settings.',
'manage' => 'Manage system settings.'
),
'users' => array(
'all' => array(
'view' => 'View all users.',
'manage' => 'Manage all users.'
)
),
'associations' => array(
'generalInformation' => array(
'all' => array(
'view' => 'View general information of all associations.',
'manage' => 'Manage general information of all associations.'
),
'own' => array(
'view' => 'View general information of the association the user is a member of.',
'manage' => 'Manage general information of the association the user is a member of.'
)
)
));
I'm trying to collapse / cascade the keys into a one-dimension array like so:
array(
'system_view',
'system_manage',
'users_all_view',
'users_all_manage',
'associations_generalInformation_all_view',
'associations_generalInformation_all_manage',
'associations_generalInformation_own_view',
'associations_generalInformation_own_manage'
)
I could use nested loops, but the array will be an undefined number of dimensions.
This is the closest I've gotten:
public function iterateKeys(array $array, $joiner, $prepend = NULL) {
if (!isset($formattedArray)) { $formattedArray = array(); }
foreach ($array as $key => $value) {
if(is_array($value)) {
array_push($formattedArray, $joiner . $this->iterateKeys($value, $joiner, $key));
} else {
$formattedArray = $prepend . $joiner . $key;
}
}
return $formattedArray;
}
Any ideas?
I think this should do it:
public function iterateKeys(array $array, $joiner, $prepend = NULL) {
if (!isset($formattedArray)) {
$formattedArray = array();
}
foreach ($array as $key => $value) {
if(is_array($value)) {
$formattedArray = array_merge($formattedArray, $this->iterateKeys($value, $joiner, $prepend . $joiner . $key));
} else {
$formattedArray[] = $prepend . $joiner . $key;
}
}
return $formattedArray;
}
Since the recursive call returns an array, you need to use array_merge to combine it with what you currently have. And for the non-array case, you need to push the new string onto the array, not replace the array with a string.
try this:
function flattern(&$inputArray, $tmp = null, $name = '')
{
if ($tmp === null) {
$tmp = $inputArray;
}
foreach($tmp as $index => $value) {
if (is_array($value)) {
flattern($inputArray, $value, $name.'_'.$index);
if (isset($inputArray[$index])) {
unset($inputArray[$index]);
}
} else {
$inputArray[$name.'_'.$index] = $value;
}
}
return $inputArray;
}
var_dump(flattern($permissionTypes));
function flattenWithKeys(array $array, array $path = []) {
$result = [];
foreach ($array as $key => $value) {
$currentPath = array_merge($path, [$key]);
if (is_array($value)) {
$result = array_merge($result, flattenWithKeys($value, $currentPath));
} else {
$result[join('_', $currentPath)] = $value;
}
}
return $result;
}
$flattened = flattenWithKeys($permissionTypes);
Working fine for me.
function array_key_append($source_array, $return_array = array(), $last_key = '', $append_with = "#")
{
if(is_array($source_array))
{
foreach($source_array as $k => $v)
{
$new_key = $k;
if(!empty($last_key))
{
$new_key = $last_key . $append_with . $k;
}
if(is_array($v))
{
$return_array = array_key_append($v, $return_array, $new_key, $append_with);
} else {
$return_array[$new_key] = $v;
}
}
}
return $return_array;
}
$StandardContactRequestDataforALL = array_key_append($StandardContactRequestDataforALL, $return_array = array(), $last_key = '', $append_with = "#");
Currently stuck trying to get the last part working, wanting to get all array keys returned where the value exists.
Test data
$testArr = array(
'id' => '249653315914',
'title' => 'testing',
'description' => 'testing',
'usernames' => array('jake', 'liam', 'john'),
'masterNames' => array('jake'),
'data' => array(
'aliases' => array(
'jake'
)
)
);
Method
recursive_search('jake', $testArr);
function recursive_search($needle, $haystack, $child = false) {
$values = array();
foreach($haystack as $key => $value) {
$current_key = $key;
if($needle === $value OR (is_array($value) && $children = recursive_search($needle, $value, true) !== false)) {
if($child) {
if($needle == $value) {
return true;
}
echo "children: $child, current_key: $current_key ";
return $current_key;
}
echo "current_key: $current_key, key: $key <br>";
$values[] = $current_key;
}
}
if(!empty($values)) {
return $values;
}
return false;
}
Output
current_key: usernames, key: usernames
current_key: masterNames, key: masterNames
children: 1, current_key: aliases current_key: data, key: data
array (size=3)
0 => string 'usernames' (length=5)
1 => string 'masterNames' (length=8)
2 => string 'data' (length=4)
Expected
array(
'usernames',
'masterNames',
'data' => array('aliases')
)
I'm losing track on the $child part I think, somewhere I should be returning something and assigning it but I think I've looked at this to long and overlooking the obvious.
Any help is awesome.
$testArr = array(
'id' => '249653315914',
'title' => 'jake',
'description' => 'testing',
'usernames' => array('jake', 'liam', 'john'),
'masterNames' => array('jake'),
'data' => array(
'aliases' => array(
'jake'
),
'aliases2' => array('level3' => array('jake'))
)
);
function recursive_search($needle, $haystack) {
$return = array();
if (is_array($haystack))
foreach($haystack as $key => $value)
{
if (is_array($value))
{
$child = recursive_search($needle, $value);
if (is_array($child) && !empty($child))
$return = array_merge($return, array($key => $child));
elseif ($child) $return[] = $key;
}
elseif ($value === $needle)
if (is_integer($key))
return true;
else
$return[] = $key;
}
elseif ($haystack === $needle)
return true;
return $return;
}
Output
Array
(
[0] => title
[1] => usernames
[2] => masterNames
[data] => Array
(
[0] => aliases
[aliases2] => Array
(
[0] => level3
)
)
)
Testing is needed, no warranty that it will work in all cases. Also, in the cases like this array('level3' => array('jake'), 'jake'), it will not go deeper to level3 and so on, as 'jake' is present in the original array. Please mention if that is not a desired behavior.
Edit by Bankzilla: To work with objects
function recursive_search($needle, $haystack) {
$return = array();
if (is_array($haystack) || is_object($haystack)) {
foreach($haystack as $key => $value) {
if (is_array($value) || is_object($value)) {
$child = recursive_search($needle, $value);
if ((is_array($child) || is_object($child)) && !empty($child)) {
$return = array_merge($return, array($key => $child));
} elseif ($child) {
$return[] = $key;
}
}
elseif ($value === $needle) {
if (is_integer($key)) {
return true;
} else {
$return[] = $key;
}
}
}
} elseif ($haystack === $needle) {
return true;
}
return $return;
}
the things I thought you did wrong was checking for if current $value is an array and doing a recursive search on it, while doing nothing with the return value of it (which should be the array either false ) , here I used a more step by step approach (well at least I thought so )
this works for a "tree with more branches"
EDIT
function recursive_search($needle, $haystack) {
$values = array();
foreach($haystack as $key => $value) {
if(is_array($value)) {
$children = $this->recursive_search($needle, $value);
if($children !== false){
if(!is_bool($children) and !empty($children)){
$key = array($key => $children);
}
$values[] = $key;
}
} else if(strcmp($needle, $value) == 0 ){
if(is_int($key))
return true;
else
$vaues[] = $key;
}
}
if(!empty($values)) {
return $values;
}
return false;
}
got rid of the "0"s in the array, thx Cheery for the hint (from his response)