Artihmetical operations with json elements in Laravel - php

I try to make some operations with JSON files.
For example I intend to subtract them. But the problem is there can be different key elements.
$a1=[
{"cat":2,"total":1},
{"cat":11,"total":23},
{"cat":13,"total":30}
];
$a2=[
{"cat":2,"total":15},
{"cat":11,"total":13},
{"cat":15,"total":70},
{"cat":16,"total":40}
];
and result must be
$result=[
{"cat":2,"total":14},
{"cat":11,"total":-10},
{"cat":13,"total":-30},
{"cat":15,"total":70},
{"cat":16,"total":40}
]
I have tried to take elements in a loop but I could not.
would you please show me the way to make this work ?

First, the code:
<?php
$json1 = '[{"cat":2,"total":1},{"cat":11,"total":23},{"cat":13,"total":30}]';
$json2 = '[{"cat":2,"total":15},{"cat":11,"total":13},{"cat":15,"total":70},{"cat":16,"total":40}]';
$first = json_decode($json1, true);
$second = json_decode($json2, true);
$difference = [];
foreach ($second as $s) {
$key = null;
$f = array_filter($first, function($v, $k) use ($s, &$key){
$result = $v["cat"] === $s["cat"];
if ($result) $key = $k;
return $result;
}, ARRAY_FILTER_USE_BOTH);
$total = $s["total"] - (count($f) ? $f[$key]["total"] : 0);
$difference[]=["cat" => $s["cat"], "total" => $total];
}
foreach ($first as $f) {
$s = array_filter($second, function($v, $k) use ($f) {
return $v["cat"] === $f["cat"];
}, ARRAY_FILTER_USE_BOTH);
if (!count($s)) {
$difference[]=["cat" => $f["cat"], $total => -$f["total"]];
}
}
echo var_dump($difference); //I tested by echoing it out
Explanation:
I initialize two variables with the JSON inputs you have defined
I decode them both using json_decode and I set the second parameter to true to make sure they will be associative arrays
I loop the second
I initialize the key with null
I search for a match in the first using $s and $key so I will see these variables in the closure of the function
If I find such a match then I initialize $key with it
I subtract the first total (if exists) from the total
I add a new item with the matching category and the correct new total
I loop the first
I search a match in the second using $f
If no matches were found, I add the item with the matching category and negative total
In short, I loop the second and subtract the matching first if they exist, defaulting to 0 and then I loop the first to add the elements that do not have a match in the second.

Related

Nested foreach returns ONLY the last element of array

This function takes an array of strings. For every string, it searches an xml document and returns an array. It appends every array found in a separate array $finalarray.
I am trying to store the search results of the sample.xml file in the $finalarray array. It always stores the search result of the last element (the 3rd value, 'c') for some reason. I also tried with for loop to no avail. For some reason, the xpath query search is reset and will be applied to the last element.
// array to capture xml search result
$sampleflightdataxml = simplexml_load_file('sample.xml');
$flightList = ['a', 'b', 'c'];
$temparray = [];
$finalarray = [];
$xmlres = [];
foreach ($flightList as $flight) {
$xmlres = $sampleflightdataxml->xpath("//flights/FlightOrigin[contains(text(), '{$flight}')]/ancestor::flights | //flights//FlightDest[contains(text(), '{$flight}')]/ancestor::flight");
if (count($xmlres) > 0) { // if flight data is found
foreach ($xmlres as $key => $val) {
$temparray[$key]['FlightOrigin'] = (string)$val->FlightOrigin;
$temparray[$key]['FlightDest'] = (string)$val->FlightDest;
}
$finalarray[] = $temparray;
}
}
return $finalarray;

Replace array value with more than one values

I have an array like this,
$array = array(
1,2,3,'4>12','13.1','13.2','14>30'
);
I want to find any value with an ">" and replace it with a range().
The result I want is,
array(
1,2,3,4,5,6,7,8,9,10,11,12, '13.1', '13.2', 14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30
);
My understanding:
if any element of $array has '>' in it,
$separate = explode(">", $that_element);
$range_array = range($separate[0], $separate[1]); //makes an array of 4 to 12.
Now somehow replace '4>12' of with $range_array and get a result like above example.
May be I can find which element has '>' in it using foreach() and rebuild $array again using array_push() and multi level foreach. Looking for a more elegant solution.
You can even do it in a one-liner like this:
$array = array(1,2,3,'4>12','13.1','13.2','14>30');
print_r(array_reduce(
$array,
function($a,$c){return array_merge($a,#range(...array_slice(explode(">","$c>$c"),0,2)));},
[]
));
I avoid any if clause by using range() on the array_slice() array I get from exploding "$c>$c" (this will always at least give me a two-element array).
You can find a little demo here: https://rextester.com/DXPTD44420
Edit:
OK, if the array can also contain non-numeric values the strategy needs to be modified: Now I will check for the existence of the separator sign > and will then either merge some cells created by a range() call or simply put the non-numeric element into an array and merge that with the original array:
$array = array(1,2,3,'4>12','13.1','64+2','14>30');
print_r(array_reduce(
$array,
function($a,$c){return array_merge($a,strpos($c,'>')>0?range(...explode(">",$c)):[$c]);},
[]
));
See the updated demo here: https://rextester.com/BWBYF59990
It's easy to create an empty array and fill it while loop a source
$array = array(
1,2,3,'4>12','13.1','13.2','14>30'
);
$res = [];
foreach($array as $x) {
$separate = explode(">", $x);
if(count($separate) !== 2) {
// No char '<' in the string or more than 1
$res[] = $x;
}
else {
$res = array_merge($res, range($separate[0], $separate[1]));
}
}
print_r($res);
range function will help you with this:
$array = array(
1,2,3,'4>12','13.1','13.2','14>30'
);
$newArray = [];
foreach ($array as $item) {
if (strpos($item, '>') !== false) {
$newArray = array_merge($newArray, range(...explode('>', $item)));
} else {
$newArray[] = $item;
}
}
print_r($newArray);

Sort a flat array in recurring ascending sequences

I am trying to sort it in a repeating, sequential pattern of numerical order with the largest sets first.
Sample array:
$array = [1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3];
In the above array, I have the highest value of 5 which appears twice so the first two sets would 1,2,3,4,5 then it would revert to the second, highest value set etc.
Desired result:
[1,2,3,4,5,1,2,3,4,5,1,2,3,4,1,2,4]
I am pretty sure I can split the array into chunks of the integer values then cherrypick an item from each subarray sequentially until there are no remaining items, but I just feel that this is going to be poor for performance and I don't want to miss a simple trick that PHP can already handle.
Here's my attempt at a very manual loop using process, the idea is to simply sort the numbers into containers for array_unshifting. I'm sure this is terrible and I'd love someone to do this in five lines or less :)
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
// Build the container array
$numbers = array_fill_keys(array_unique($array),array());
// Assignment
foreach( $array as $number )
{
$numbers[ $number ][] = $number;
}
// Worker Loop
$output = array();
while( empty( $numbers ) === false )
{
foreach( $numbers as $outer => $inner )
{
$output[] = array_shift( $numbers[ $outer ] );
if( empty( $numbers[ $outer ] ) )
{
unset( $numbers[ $outer ] );
}
}
}
var_dump( $output );
I think I'd look at this not as a sorting problem, but alternating values from multiple lists, so rather than coming up with sets of distinct numbers I'd make sets of the same number.
Since there's no difference between one 1 and another, all you actually need is to count the number of times each appears. It turns out PHP can do this for you with aaray_count_values.
$sets = array_count_values ($input);
Then we can make sure the sets are in order by sorting by key:
ksort($sets);
Now, we iterate round our sets, counting down how many times we've output each number. Once we've "drained" a set, we remove it from the list, and once we have no sets left, we're all done:
$output = [];
while ( count($sets) > 0 ) {
foreach ( $sets as $number => $count ) {
$output[] = $number;
if ( --$sets[$number] == 0 ) {
unset($sets[$number]);
}
}
}
This algorithm could be adapted for cases where the values are actually distinct but can be put into sets, by having the value of each set be a list rather than a count. Instead of -- you'd use array_shift, and then check if the length of the set was zero.
You can use only linear logic to sort using php functions. Here is optimized way to fill data structures. It can be used for streams, generators or anything else you can iterate and compare.
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
$chunks = [];
$index = [];
foreach($array as $i){
if(!isset($index[$i])){
$index[$i]=0;
}
if(!isset($chunks[$index[$i]])){
$chunks[$index[$i]]=[$i];
} else {
$chunks[$index[$i]][] = $i;
}
$index[$i]++;
}
$result = call_user_func_array('array_merge', $chunks);
print_r($result);
<?php
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
while($array) {
$n = 0;
foreach($array as $k => $v) {
if($v>$n) {
$result[] = $n = $v;
unset($array[$k]);
}
}
}
echo implode(',', $result);
Output:
1,2,3,4,5,1,2,3,4,5,1,2,3,4,1,2,4
New, more elegant, more performant, more concise answer:
Create a sorting array where each number gets its own independent counter to increment. Then use array_multisort() to sort by this grouping array, then sort by values ascending.
Code: (Demo)
$encounters = [];
foreach ($array as $v) {
$encounters[] = $e[$v] = ($e[$v] ?? 0) + 1;
}
array_multisort($encounters, $array);
var_export($array);
Or with a functional style with no global variable declarations: (Demo)
array_multisort(
array_map(
function($v) {
static $e;
return $e[$v] = ($e[$v] ?? 0) + 1;
},
$array
),
$array
);
var_export($array);
Old answer:
My advice is functionally identical to #El''s snippet, but is implemented in a more concise/modern/attractive fashion.
After ensuring that the input array is sorted, make only one pass over the array and push each re-encountered value into its next row of values. The $counter variable indicates which row (in $grouped) the current value should be pushed into. When finished looping and grouping, $grouped will have unique values in each row. The final step is to merge/flatten the rows (preserving their order).
Code: (Demo)
$grouped = [];
$counter = [];
sort($array);
foreach ($array as $v) {
$counter[$v] = ($counter[$v] ?? -1) + 1;
$grouped[$counter[$v]][] = $v;
}
var_export(array_merge(...$grouped));

How do I search if a value is in a range, inside a multi-dimensional array in PHP?

It's a simple question, but puzzling me:
$myarray = array(
array(10,20),
array(299, 315),
array(156, 199)
);
How do I check if given $x , lies in between, in any of those particular individual array values? I want to search each individual entry array.
For Example, I want to search, if $x is somewhere between: 10 to 20 and then between 299 to 315 and then between 156 to 199.
Try this:
function is_in_array_range($array, $search) {
foreach ($array as $value) {
$min = min($value);
$max = max($value);
if ($search >= $min && $search <= $max) {
return true;
}
}
return false;
}
$myarray = array(
array(10,20),
array(299, 315),
array(156, 199)
);
is_in_array_range($myarray, 9); // Returns false
is_in_array_range($myarray, 11); // Returns true
The function is_in_array_range() will take two arguments. The array, and the value you want to check is in the range.
When it enters, it will loop over all elements in the array. Every time it gets the highest and lowest value of the nested array (min() and max() function), and checks if the value you are looking for is between them. If this is the case, return true (this also stops the function). If true is never reached, the value is not found, so at the end of the function, return false.
this will do it code
foreach($myarray as $value)
{
if(in_array("10", $value, true))
{
echo "Got 10";
}
}

Finding the first, last and nth row in a foreach loop

I was wondering if PHP has a gracefull method to find the first, last and/or nth row in a foreach loop.
I could do it using a counter as follows:
$i = 0;
$last = count($array)-1;
foreach ($array as $key => $row) {
if ($i == 0) {
// First row
}
if ($i == $last) {
// Last row
}
$i++;
}
But somehow this feels like a bit of a dirty fix. Any solutions or suggestions?
Edit
As suggested in the comments I moved the count($array) outside the loop.
foreach ($array as $key => $row) {
$index = array_search($key, array_keys($array));
if ($index == 0) {
// First row
}
if ($index == count($array) - 1) {
// Last row
}
}
In php we have current and end function to get first and last value of array.
<?php
$transport = array('foot', 'bike', 'car', 'plane');
echo $first = current($transport); // 'foot';
echo $end = end($transport); // 'plane';
?>
Modified :
Easy way without using current or end or foreach loop:
$last = count($transport) - 1;
echo "First : $transport[0]";
echo "</br>";
echo "Last : $transport[$last]";
Using Arrays
For the first element in an array you can simply seek $array[0];. Depending on the array cursor you can also use current($array);
For the middle of an array you can use a combination of array_search() and array_keys().
For the end of an array you can use end($array); noting that this aslso moves the array cursor to the last element as well (as opposed to simply returning the value).
Using Iterators
However ArrayIterator's may also work well in your case:
The first element is available at ArrayIterator::current(); once constructed. (If you're halfway through the iterator you'll need to reset().)
For the n'th or a middle element you can use an undocumented Iterator::seek($index); method.
For the last element you can use a combination of seek() and count().
For example:
$array = array('frank' => 'one',
'susan' => 'two',
'ahmed' => 'three');
$arrayobject = new ArrayObject($array);
$iterator = $arrayobject->getIterator();
// First:
echo $iterator->current() . PHP_EOL;
// n'th: (taken from the documentation)
if($iterator->valid()){
$iterator->seek(1); // expected: two, output: two
echo $iterator->current() . PHP_EOL; // two
}
// last:
$iterator->seek(count($iterator)-1);
echo $iterator->current() . PHP_EOL;
$arr = ["A", "B", "C", "D", "E"];
reset($arr);
// Get First Value From Array
echo current($arr);
// Get Last Value From Array
echo end($arr);
Visit below link for details of above used functions.
reset() : http://php.net/manual/en/function.reset.php
current() : http://php.net/manual/en/function.current.php
end() : http://php.net/manual/en/function.end.php

Categories