I would like te perform a calculation on the following array
$array = [
"calculation" => [
"add" => [
"ceil" => [
"multiply" => [
9.95,
"90%"
]
],
0.95
]
]
];
eventually the calculation would traverse into:
1. add(ceil(multiply(9.95, 90%)), 0.95)
2. ceil(multiply(9.95, 90%)) + 0.95
3. ceil(8.955) + 0.95
4. 9 + 0.95
5. 9.95
I've tried recursively looping through the array using array_walk_recursive and custom functions which basically to the same.
but the problem is that the add cannot calculate the ceil before it has calculated the multiply.
So how can i recusively in reversed order calculate all the parts of the array?
I'm slowly loosing my mind over this and starting to question if it's even possible.
All ideas are greatly appreciated
Another option, that verify the indexes of the array are valid functions and allows add and multiple with more than 2 values.
function multiply(...$a)
{
return array_product($a);
}
function add(...$a)
{
return array_sum($a);
}
function applyCalculation(array $data)
{
$firstKey = array_keys($data)[0];
$arguments = [];
foreach ($data[$firstKey] as $name => $value) {
// if argument is array with existing function, we pass it recursively
if (function_exists($name) && is_array($value)) {
$result = applyCalculation([$name => $value]);
$result = is_array($result) ? $result : [$result];
$arguments = array_merge($arguments, $result);
} elseif (!is_array($value)) {
// if not array, just append to arguments
$value = strpos($value, '%') !== false ? str_replace('%','',$value) / 100 : $value;
$arguments[] = $value;
} elseif (is_array($value)) {
// error here, the index is not a valid function
}
}
return $firstKey(...$arguments);
}
echo applyCalculation($array['calculation']);
So it will work for your example:
$data = [
"calculation" => [
"add" => [
"ceil" => [
"multiply" => [
9.95,
'90%',
]
],
0.95
]
]
];
// will give you 9.95
But it also works for more complex cases::
$array = [
"calculation" => [
"add" => [
"ceil" => [
"add" => [
"multiply" => [
9.95,
'90%'
],
"add" => [
1,
3
],
]
],
0.95,
3
]
]
];
// will give you 16.95
Assuming that you have this structure, you may use this code. If you need more than 2 values to be "added" or "multiplied" you may need to use some splat operator or change the logic a little bit:
<?php
$array = [
"calculation" => [
"add" => [
"ceil" => [
"multiply" => [
9.95,
"90%"
]
],
0.95
]
]
];
function add($a, $b) {
return $a + $b;
}
function multiply($a, $b) {
return $a * $b;
}
function calculation($arr, $operation = null) {
$elements = [];
foreach($arr as $k => $value) {
if (is_numeric($k)) {
// change % with valid numeric value
if(strpos($value, '%')) {
$value = str_replace('%','',$value) / 100;
}
//if there are no operations, append to array to evaluate under the operation
$elements[] = $value;
} else {
//if there are operations, call the function recursively
$elements[] = calculation($value, $k);
}
}
if ($operation !== null) {
return call_user_func_array($operation, $elements);
}
return $elements[0];
}
echo calculation($array['calculation']);
Related
I'm working on project and I'm trying to refactor a json object By array of $keys
for example:
as input:
{
"message": "User Detail",
"code": 200,
"error": false,
"results": {
"user": {
"id": 2,
"name": "ali",
"country": {
"id": 50,
"name": "EGY"
}
},
"access_token": "=PQLkHJYIXB2uKbCq4sXIjD2GpBU2o"
}
}
as input: array of $keys
$kyes = [
'code',
'user'=>[
'id',
'country'=>['name']
]
]
expect to return:
{
"code": 200,
"user": {
"id": 2,
"country": {
"name": "egy",
}
}
}
code I have tried:
public function filter_multidimensional(array $array, array $keys){
$val = [];
foreach ($array as $key => $value) {
if (in_array($key,$keys)){
$val[$key] = $value;
}elseif (is_array($value)) {
$val[$key] = $this->filter_multidimensional($keys,$value);
}
}
return $val;
}
//-------
$this->filter_multidimensional($json,['code','user'=>['id','country'=>['name']]])
Unfortunately output:
update 1
the json input is not const, so my code must be adapt. and that's I'm trying to do.
Thanks for #404-not-found his code was amazing but missing a small thing
which is in this code
if (is_array($key)) {
$val[$objectKey] = filter_multidimensional($array, $key);
}
you still give the find function the base array which will return the first value it will find in the case of ['user'=>['id',"country"=>['id']]] and the solution for this will be passing the parent array of the object key
So the full code will be
<?php
function filter_multidimensional(array $array, array $keys) {
if (empty($keys) || empty($array)) {
return [];
}
$val = [];
// In the structure of $keys, both key and value are technically "keys".
// In the example `['code','user'=>['id']]`, 'code' and 'id' are both array *values*,
// while 'user' is a key.
//
// So, $objectKey is a search key which contains sub-keys, while $key is just a regular string.
foreach ($keys as $objectKey => $key) {
// If $key is an array, then recursively search, and save the results to the $objectKey string.
if (is_array($key)) {
$val[$objectKey] = filter_multidimensional(findTill($array,$objectKey), $key);
}
// If the desired key exists, then save the value.
else if (array_key_exists($key, $array)) {
$val[$key] = $array[$key];
}
// Otherwise, $key is a string, but it does not exist at this level of $array,
// so search the next-level up in $array, and merge the results into this level of $val.
else {
$val = array_merge($val, filter_multidimensional(nextLevel($array), [$key]));
}
}
return $val;
}
function findTill($array,$key) {
if (array_key_exists($key, $array)) {
return $array[$key];
}
return findTill(nextLevel($array),$key);
}
/**
* Create an array that contains only the array values from $array.
*
* Eg: If given ['a' => '1', 'b' => ['foo' => '2'], 'c' => ['hello' => 'world']],
* then return ['foo' => '2', 'hello' => 'world']
*
* #param array $array
* #return array
*/
function nextLevel(array $array) {
// Merge all of the array values together into one array
return array_reduce(
// Strip the keys
array_values(
// Filter to return only keys where the value is an array
array_filter(
$array,
function ($value) {
return is_array($value);
}
)
),
'array_merge',
[]
);
}
//-------
$obj = [
"message" => "User Detail",
"code" => 200,
"error" => false,
"results" => [
"user" => [
"id" => 2,
"name" => "ali",
"country" => [
"id" => 50,
"name" => "EGY",
],
],
"access_token" => "=PQLkHJYIXB2uKbCq4sXIjD2GpBU2o",
],
];
$result = filter_multidimensional($obj,['code','user'=>['id','country'=>['id','name']],"access_token"]);
I believe this method works. I flipped your logic, and instead of searching the $array to see if it's keys match any of those in $keys, I instead recursively searched $keys to see if $array had any matching values.
function filter_multidimensional(array $array, array $keys) {
if (empty($keys) || empty($array)) {
return [];
}
$val = [];
// In the structure of $keys, both key and value are technically "keys".
// In the example `['code','user'=>['id']]`, 'code' and 'id' are both array *values*,
// while 'user' is a key.
//
// So, $objectKey is a search key which contains sub-keys, while $key is just a regular string.
foreach ($keys as $objectKey => $key) {
// If $key is an array, then recursively search, and save the results to the $objectKey string.
if (is_array($key)) {
$val[$objectKey] = filter_multidimensional($array, $key);
}
// If the desired key exists, then save the value.
else if (array_key_exists($key, $array)) {
$val[$key] = $array[$key];
}
// Otherwise, $key is a string, but it does not exist at this level of $array,
// so search the next-level up in $array, and merge the results into this level of $val.
else {
$val = array_merge($val, filter_multidimensional(nextLevel($array), [$key]));
}
}
return $val;
}
/**
* Create an array that contains only the array values from $array.
*
* Eg: If given ['a' => '1', 'b' => ['foo' => '2'], 'c' => ['hello' => 'world']],
* then return ['foo' => '2', 'hello' => 'world']
*
* #param array $array
* #return array
*/
function nextLevel(array $array) {
// Merge all of the array values together into one array
return array_reduce(
// Strip the keys
array_values(
// Filter to return only keys where the value is an array
array_filter(
$array,
function ($value) {
return is_array($value);
}
)
),
'array_merge',
[]
);
}
//-------
$result = filter_multidimensional($obj,['code','user'=>['id','country'=>['name']]]);
You could use a combination of data_get and data_set to get what you want.
Change the input a bit so it's more consistent. Dot notation would be easiest.
$array = [
"message" => "User Detail",
"code" => 200,
"error" => false,
"results" => [
"user" => [
"id" => 2,
"name" => "ali",
"country" => [
"id" => 50,
"name" => "EGY",
],
],
"access_token" => "=PQLkHJYIXB2uKbCq4sXIjD2GpBU2o",
],
];
$keys = [
'code' => 'code',
'user.id' => 'results.user.id',
'user.country.name' => 'results.user.country.name',
];
$results = [];
foreach ($keys as $set_position => $get_position) {
data_set($results, $set_position, data_get($array, $get_position));
}
I have two arrays. $array2 to check whether $array1 value exist in 'slug'.
$array1 = [
0 => "tenants",
1 => "modules",
];
$array2 = [
"child" => [
"prefix" => "tenants",
"slug" => "tenants",
"child" => [
[
"prefix" => "modules/{id}",
"slug" => "modules"
],
[
"prefix" => "fetch/{id}",
"slug" => "fetch"
],
],
],
];
My Code:
foreach ($array1 as $value) {
array_walk_recursive($array2['child'],
function ($item, $key) use (&$result, &$res, &$value) {
if ($key == "slug") {
if ($item == $value) {
$res[] = $item;
}
}
if ($key == 'prefix') {
$result[] = $item;
}
});
}
Here I used array_walk_recursive to check every if array1 is exist in slug. But i want to get also it's prefix.
In my code above It will not get the modules/{id} since it's not equal to slug.
Example:
$array1 = [
0 => "tenants",
1 => "modules",
];
Result:
array:3 [▼
1 => "tenants"
2 => "modules/{id}"
]
Code: https://3v4l.org/E18Uq
Happy coding.
You don't need to use array_walk_recursive just use custom function as:
function getFromChild($item, $asd) {
if (!is_array($item)) return [];
$res = [];
if ($item["slug"] == $asd) // if have slug check and add if needed
$res[] = $item['prefix'];
if (isset($item['child']))
foreach($item['child'] as $child) // for all child loop and add
$res = array_merge($res, getFromChild($child, $asd));
return $res;
}
Now you can call it with:
$res = [];
foreach ($array1 as $asd) {
$res = array_merge($res, getFromChild($array2, $asd));
}
Live example: 3v4l
I want to combine two different multi-dimensional arrays, with one providing the correct structure (keys) and the other one the data to fill it (values).
Notice that I can't control how the arrays are formed, the structure might vary in different situations.
$structure = [
"a",
"b" => [
"b1",
"b2" => [
"b21",
"b22"
]
]
];
$data = [A, B1, B21, B22];
Expected result:
$array = [
"a" => "A",
"b" => [
"b1" => "B1",
"b2" => [
"b21" => "B21",
"b22" => "B22"
]
]
];
You can use the following code, however it will only work if number of elements in $data is same or more than $structure.
$filled = 0;
array_walk_recursive ($structure, function (&$val) use (&$filled, $data) {
$val = array( $val => $data[ $filled ] );
$filled++;
});
print_r( $structure );
Here is a working demo
You can try by a recursive way. Write a recursive method which takes an array as first argument to alter and the data set as its second argument. This method itself call when any array element is another array, else it alters the key and value with the help of data set.
$structure = [
"a",
"b" => [
"b1",
"b2" => [
"b21",
"b22"
]
]
];
$data = ['A', 'B1', 'B21', 'B22'];
function alterKey(&$arr, $data) {
foreach ($arr as $key => $val) {
if (!is_array($val)) {
$data_key = array_search(strtoupper($val), $data);
$arr[$val] = $data[$data_key];
unset($arr[$key]);
} else {
$arr[$key] = alterKey($val, $data);
}
}
ksort($arr);
return $arr;
}
alterKey($structure, $data);
echo '<pre>', print_r($structure);
Working demo.
This should work.
$structure = [
"a",
"b" => [
"b1",
"b2" => [
"b21",
"b22"
]
]
];
$new_structure = array();
foreach($structure as $key =>$value)
{
if(!is_array($value))
{
$new_structure[$value]= $value;
}else{
foreach($value as $k =>$v)
{
if(!is_array($v))
{
$new_structure[$key][$v]=$v;
}else
{
foreach($v as $kk => $vv)
{
$new_structure[$k][$vv]=$vv;
}
}
}
}
}
print_r($new_structure);exit;
Use
$array=array_merge($structure,$data);
for more information follow this link
how to join two multidimensional arrays in php
I use TPP API for check domain availability and domain register but i receive response in string.
Get Session, return stringOK: t73484678463765
Domain check, return string woohoo123.nz: OK: Minimum=1&Maximum=2
In other case, return string woohoo123.nz: ERR: 102, This is message
When It return OK it has & in child but when ERR that time it has ,
I want convert return string into array
such as input woohoo123.nz: OK: Minimum=1&Maximum=2 and output following array
[
'woohoo123.nz' => [
'OK' => [
'Minimum' => 1,
'Maximum' => 2,
]
]
]
input woohoo123.nz: ERR: 102, This is message and output following array
[
'woohoo123.nz' => [
'ERR' => [
'code' => 102,
'message' => 'This is message',
]
]
]
I like more to reuse code, I prefer recursive and callback but not sure in this case.
Not 100% sure if this is what you are looking for. It works for your examples, but will only continue to work if the input strings follow that format strictly.
function stringToArray($inputStr) {
$array = [];
$topComponents = explode(': ',$inputStr);
$parametersStr = $topComponents[count($topComponents) -1];
if (strpos($parametersStr,'&') !== FALSE) {
$tmpArr = explode('&',$parametersStr);
foreach ($tmpArr as $val) {
$comp = explode('=',$val);
$array[$comp[0]] = $comp[1];
}
} else if ($topComponents[count($topComponents) - 2] === "ERR") {
$tmpArray = explode('ERR: ',$parametersStr);
$tmpArray = explode(', ',$tmpArray[0]);
$array = [
"code" => intval($tmpArray[0]),
"message" => $tmpArray[1]
];
} else {
$array = $parametersStr;
}
for ($i=count($topComponents) -2; $i >= 0; $i--) {
$newArray = [];
$newArray[$topComponents[$i]] = $array;
$array = $newArray;
}
return $array;
}
print_r(stringToArray("OK: t73484678463765"));
I'm trying to find (or create) a function. I have a multidimensional array:
$data_arr = [
"a" => [
"aa" => "abfoo",
"ab" => [
"aba" => "abafoo",
"abb" => "abbfoo",
"abc" => "abcfoo"
],
"ac" => "acfoo"
],
"b" => [
"ba" => "bafoo",
"bb" => "bbfoo",
"bc" => "bcfoo"
],
"c" => [
"ca" => "cafoo",
"cb" => "cbfoo",
"cc" => "ccfoo"
]
];
And I want to access a value using a single-dimentional array, like this:
$data_arr_call = ["a", "ab", "abc"];
someFunction( $data_arr, $data_arr_call ); // should return "abcfoo"
This seems like there's probably already a function for this type of thing, I just don't know what to search for.
Try this
function flatCall($data_arr, $data_arr_call){
$current = $data_arr;
foreach($data_arr_call as $key){
$current = $current[$key];
}
return $current;
}
OP's Explanation:
The $current variable gets iteratively built up, like so:
flatCall($data_arr, ['a','ab','abc']);
1st iteration: $current = $data_arr['a'];
2nd iteration: $current = $data_arr['a']['ab'];
3rd iteration: $current = $data_arr['a']['ab']['abc'];
You could also do if ( isset($current) ) ... in each iteration to provide an error-check.
You can use this function that avoids the copy of the whole array (using references), is able to return a NULL value (using array_key_exists instead of isset), and that throws an exception when the path doesn't exist:
function getItem(&$array, $path) {
$target = &$array;
foreach($path as $key) {
if (array_key_exists($key, $target))
$target = &$target[$key];
else throw new Exception('Undefined path: ["' . implode('","', $path) . '"]');
}
return $target;
}
demo:
$data = [
"a" => [
"aa" => "abfoo",
"ab" => [
"aba" => "abafoo",
"abb" => NULL,
"abc" => false
]
]
];
var_dump(getItem($data, ['a', 'ab', 'aba']));
# string(6) "abafoo"
var_dump(getItem($data, ['a', 'ab', 'abb']));
# NULL
var_dump(getItem($data, ['a', 'ab', 'abc']));
# bool(false)
try {
getItem($data, ['a', 'ab', 'abe']);
} catch(Exception $e) {
echo $e->getMessage();
}
# Undefined path: ["a","ab","abe"]
Note that this function can be improved, for example you can test if the parameters are arrays.
Wanted to post an even more elegant solution: array_reduce
$data_arr = [
"a" => [
...
"ab" => [
...
"abc" => "abcfoo"
],
...
],
...
];
$result = array_reduce(["a", "ab", "abc"], function($a, $b) {
return $a[$b];
}, $data_arr);
// returns "abcfoo"
I've been using Javascript's Array.reduce() a lot lately in updating some legacy code to ES6:
JS:
const data_obj = {...};
let result = ['a','ab','abc'].reduce((a, b) => a[b], data_obj);
You need a function like this:
function getValue($data_arr, $data_arr_call) {
foreach ($data_arr_call as $index) {
if (isset($data_arr[$index])) {
$data_arr = $data_arr[$index];
} else {
return false;
}
}
return $data_arr;
}
And use it like this:
$data_arr = [
"a" => [
"ab" => [
"abc" => "abbfoo",
],
],
];
$data_arr_call = ["a", "ab", "abc"];
$value = getValue($data_arr, $data_arr_call);
if ($value) {
// do your stuff
}