Laravel/PHP: Add another condition in array_reduce function - php

I have multidimensional array and I want to sum all the same value with a certain condition.
Here's what my array looks like
$input_taxes = [
[
"tax_account_id" => 65,
"checkbox" => false,
"amount" => "13950.89",
],
[
"tax_account_id" => 64,
"checkbox" => 0,
"amount" => "1920.70",
]
];
What I've researched, they used array_reduce. Below is my code:
$result = array_reduce($input_taxes, function ($carry, $item) {
if (! isset($carry[$item['tax_account_id']])) {
$chart_of_account = ChartOfAccount::where('id', $item['tax_account_id'])
->first()
->name;
$carry[$item['tax_account_id']] = [
'tax_account_id' => $item['tax_account_id'],
'amount_to_apply' => $item['amount_to_apply'],
'chart_of_account' => $chart_of_account
];
} else {
$carry[$item['tax_account_id']]['amount_to_apply'] += $item['amount_to_apply'];
}
return $carry;
});
Currently, it will sum all the same values even though it is not checked. Now I want to put a condition where it should be check first before it will sum all the same values.
NOTE: I already tried to put an if statement inside of both if and else statement but unfortunately it didn't work.
Question: How can I add a certain condition within array_reduce?

The check must be inserted at the beginning of your function.
$result = array_reduce($input_taxes, function ($carry, $item) {
if (!$item['checkbox']) {
return $carry;
}
if (! isset($carry[$item['tax_account_id']])) {
$chart_of_account = ChartOfAccount::where('id', $item['tax_account_id'])
->first()
->name;
$carry[$item['tax_account_id']] = [
'tax_account_id' => $item['tax_account_id'],
'amount_to_apply' => $item['amount_to_apply'],
'chart_of_account' => $chart_of_account
];
} else {
$carry[$item['tax_account_id']]['amount_to_apply'] += $item['amount_to_apply'];
}
return $carry;
});
I can also offer a small optimization to reduce the number of requests to the database
$chartOfAccountNames = UserProfile::query()
->whereIn('id', array_column($input_taxes, 'tax_account_id'))
->pluck('name', 'id')
->toArray();
$result = array_reduce($input_taxes, static function ($carry, $item) use ($chartOfAccountNames) {
if (!$item['checkbox']) {
return $carry;
}
if (isset($carry[$item['tax_account_id']])) {
$carry[$item['tax_account_id']]['amount_to_apply'] += $item['amount'];
return $carry;
}
$carry[$item['tax_account_id']] = [
'tax_account_id' => $item['tax_account_id'],
'amount_to_apply' => $item['amount'],
'chart_of_account' => $chartOfAccountNames[$item['tax_account_id']],
];
return $carry;
});

Related

Is that possible to know if element exists inside an array without looping through the array?

I have an array of elements with the same structure each.
Here's an example :
<?php
$arr =
[
[
"el_name" => "abcd",
"el_data" => "raw_dataè_15264df156g18df",
],
[
"el_name" => "efgh",
"el_data" => "raw_data_sd25g",
],
[
"el_name" => "ijkl",
"el_data" => "raw_data_dfdfgn48",
],
[
"el_name" => "mnop",
"el_data" => "raw_data_²545",
],
];
$new_el = [
"el_name" => "efgh",
"el_data" => "raw_data_sd25g",
];
I want to add $new_el only if it's not already present in $arr. It'd be handy if php had a array_element_exists() function ;-)
For the sake of the question I tried this:
$exists = false;
foreach($arr as $el) {
$equal=true;
foreach(array_keys($el) as $key) {
if(isset($new_el[$key]) && $new_el[$key] === $el[$key]) {
null;
//equality
} else {
$equal = false;
}
}
if($equal === true) {
$exists = true;
break;
}
}
if($exists === true ) {
echo '$new_el already exists in $arr' . PHP_EOL;
} else {
echo '$new_el doesn\'t still exists in $arr' . PHP_EOL;
$arr[] = $new_el;
}
Do you think there are more efficient, 'simpler' ways ?...
You are probably looking for in_array() function.
in_array($new_el, $arr);
returns true.
function array_element_exists($new_el,$arr){
$found = array_filter($arr, function($arr) use ($new_el) {
return $arr['el_name'] === $new_el['el_name'] &&
$arr['el_data'] === $new_el['el_data'];
});
return $found;
}
$exsist = ["el_name" => "efgh","el_data" => "raw_data_sd25g"];
$notExsist = ["el_name" => "newName","el_data" => "newData",];
//will not be added since it is already exist
if (!array_element_exists($exsist,$arr)) {
$arr[] = $exsist;
}
//will be added because it is new and not exist in `$arr`
if (!array_element_exists($notExsist,$arr)) {
$arr[] = $notExsist;
}

PHP Recursively calculate a formula in an array in reverse

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']);

php each array key into strng if child arrays add that key to the parent key [duplicate]

I have an array that looks like the following:
[
'applicant' => [
'user' => [
'username' => true,
'password' => true,
'data' => [
'value' => true,
'anotherValue' => true
]
]
]
]
What I want to be able to do is convert that array into an array that looks like:
[
'applicant.user.username',
'applicant.user.password',
'applicant.user.data.value',
'applicant.user.data.anotherValue'
]
Basically, I need to somehow loop through the nested array and every time a leaf node is reached, save the entire path to that node as a dot separated string.
Only keys with true as a value are leaf nodes, every other node will always be an array. How would I go about accomplishing this?
edit
This is what I have tried so far, but doesnt give the intended results:
$tree = $this->getTree(); // Returns the above nested array
$crumbs = [];
$recurse = function ($tree, &$currentTree = []) use (&$recurse, &$crumbs)
{
foreach ($tree as $branch => $value)
{
if (is_array($value))
{
$currentTree[] = $branch;
$recurse($value, $currentTree);
}
else
{
$crumbs[] = implode('.', $currentTree);
}
}
};
$recurse($tree);
This function does what you want:
function flattenArray($arr) {
$output = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach(flattenArray($value) as $flattenKey => $flattenValue) {
$output["${key}.${flattenKey}"] = $flattenValue;
}
} else {
$output[$key] = $value;
}
}
return $output;
}
You can see it running here.

Dynamically dig into JSON with PHP

Okay, so I need to dynamically dig into a JSON structure with PHP and I don't even know if it is possible.
So, let's say that my JSON is stored ad the variable $data:
$data = {
'actions':{
'bla': 'value_actionBla',
'blo': 'value_actionBlo',
}
}
So, to access the value of value_actionsBla, I just do $data['actions']['bla']. Simple enough.
My JSON is dynamically generated, and the next time, it is like this:
$data = {
'actions':{
'bla': 'value_actionBla',
'blo': 'value_actionBlo',
'bli':{
'new': 'value_new'
}
}
}
Once again, to get the new_value, I do: $data['actions']['bli']['new'].
I guess you see the issue.
If I need to dig two levels, then I need to write $data['first_level']['second_level'], with three, it will be $data['first_level']['second_level']['third_level'] and so on ...
Is there any way to perform such actions dynamically? (given I know the keys)
EDIT_0: Here is an example of how I do it so far (in a not dynamic way, with 2 levels)
// For example, assert that 'value_actionsBla' == $data['actions']['bla']
foreach($data as $expected => $value) {
$this->assertEquals($expected, $data[$value[0]][$value[1]]);
}
EDIT_1
I have made a recursive function to do it, based on the solution of #Matei Mihai:
private function _isValueWhereItSupposedToBe($supposedPlace, $value, $data){
foreach ($supposedPlace as $index => $item) {
if(($data = $data[$item]) == $value)
return true;
if(is_array($item))
$this->_isValueWhereItSupposedToBe($item, $value, $data);
}
return false;
}
public function testValue(){
$searched = 'Found';
$data = array(
'actions' => array(
'abort' => '/abort',
'next' => '/next'
),
'form' => array(
'title' => 'Found'
)
);
$this->assertTrue($this->_isValueWhereItSupposedToBe(array('form', 'title'), $searched, $data));
}
You can use a recursive function:
function array_search_by_key_recursive($needle, $haystack)
{
foreach ($haystack as $key => $value) {
if ($key === $needle) {
return $value;
}
if (is_array($value) && ($result = array_search_by_key_recursive($needle, $value)) !== false) {
return $result;
}
}
return false;
}
$arr = ['test' => 'test', 'test1' => ['test2' => 'test2']];
var_dump(array_search_by_key_recursive('test2', $arr));
The result is string(5) "test2"
You could use a function like this to traverse down an array recursively (given you know all the keys for the value you want to access!):
function array_get_nested_value($data, array $keys) {
if (empty($keys)) {
return $data;
}
$current = array_shift($keys);
if (!is_array($data) || !isset($data[$current])) {
// key does not exist or $data does not contain an array
// you could also throw an exception here
return null;
}
return array_get_nested_value($data[$current], $keys);
}
Use it like this:
$array = [
'test1' => [
'foo' => [
'hello' => 123
]
],
'test2' => 'bar'
];
array_get_nested_value($array, ['test1', 'foo', 'hello']); // will return 123

PHP Create breadcrumb list of every value in nested array

I have an array that looks like the following:
[
'applicant' => [
'user' => [
'username' => true,
'password' => true,
'data' => [
'value' => true,
'anotherValue' => true
]
]
]
]
What I want to be able to do is convert that array into an array that looks like:
[
'applicant.user.username',
'applicant.user.password',
'applicant.user.data.value',
'applicant.user.data.anotherValue'
]
Basically, I need to somehow loop through the nested array and every time a leaf node is reached, save the entire path to that node as a dot separated string.
Only keys with true as a value are leaf nodes, every other node will always be an array. How would I go about accomplishing this?
edit
This is what I have tried so far, but doesnt give the intended results:
$tree = $this->getTree(); // Returns the above nested array
$crumbs = [];
$recurse = function ($tree, &$currentTree = []) use (&$recurse, &$crumbs)
{
foreach ($tree as $branch => $value)
{
if (is_array($value))
{
$currentTree[] = $branch;
$recurse($value, $currentTree);
}
else
{
$crumbs[] = implode('.', $currentTree);
}
}
};
$recurse($tree);
This function does what you want:
function flattenArray($arr) {
$output = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach(flattenArray($value) as $flattenKey => $flattenValue) {
$output["${key}.${flattenKey}"] = $flattenValue;
}
} else {
$output[$key] = $value;
}
}
return $output;
}
You can see it running here.

Categories