Multidimensional sub array subtraction in PHP - php

Need subarray difference of below array
$arr = array(
array('s'=>'1','e'=>'3'),
array('s'=>'6','e'=>'7'),
array('s'=>'8','e'=>'9'),
array('s'=>'10','e'=>'14'),
array('s'=>'16','e'=>'17'),
)
if(arr[$arr[$i+1][s] - $i][e] <= 1){
//join them
}
else {
//save them as it is
}
Desired result should
$arr = array(
array('s'=>'1','e'=>'3'),
array('s'=>'6','e'=>'14'),
array('s'=>'16','e'=>'17'),
)
No consecutive (next S-E) should be 1
http://codepad.org/V8omMdn6 is where im struck at
See its like
iteration 0
6-3 = 3
so save array('s'=>'1','e'=>'3'),
iteration 1
8-7 = 1
array('s'=>'6','e'=>'9'), => discade in 2 as it
iteration 2
10-9 = 1
array('s'=>'6','e'=>'10'), => discade in 3 as it
iteration 3
10-9 = 1
array('s'=>'6','e'=>'14'),
iteration 4
16-14 = 4
array('s'=>'16','e'=>'17'),

$result = [];
foreach ($arr as $pair) {
if (empty($result) || $pair['s'] - end($result)['e'] > 1) {
$result[] = $pair;
} else {
$result[key($result)]['e'] = $pair['e'];
}
}
You might also use $last as key instead end() & key() for readability.
Using array pointer functions on $result shortens the code but uses some ugly hidden effects. end($result) returns last element of array (using key bracket with function result is possible since php5.3 I guess), but also sets the pointer, so key($result) will return correct key if needed.
While iterating you process last element of result array - this element might not be valid right away, but you don't need to look ahead. There are two scenarios for last element (+initial state condition for empty $result):
invalid: set e value from current item and process further
valid: leave it and push current item into results for further validation (unless that was the last one).

I took a very brief look at your codepen, I think what you want to achieve is to find out if the start time of a new session is within a given period from the end time of the last session, if so you would like to combine those sessions.
I think you confused yourself by trying to subtract start time of new session from end time of last session, it should be the other way round.
The way you worded the question made it even more confusing for people to understand.
If my interpretation of your question is correct, the below code should work with the test case you posted here.
function combineSession($arr){
$arrCount=count($arr)-1;
for ($i=0; $i<$arrCount; $i++){
//if the difference between s and e is less than or equal to one, then there is a consecutive series
if($arr[$i+1]['s']-$arr[$i]['e'] <= 1){
//assign the value of s at the start of a consecutive series to $temp
if (!isset($temp)){
$temp=$arr[$i]['s'];
}
//if consecutive series ends on the last sub_array, write $temp e pair to output
if ($i==$arrCount-1){
$output[]= array('s'=> $temp, 'e' => $arr[$arrCount]['e']);
}
}
//end of a consecutive series, write $temp and e pair to output, unset $temp
else if (isset($temp) && $i<$arrCount-1){
$output[]=array('s'=> $temp, 'e' => $arr[$i]['e']);
unset($temp);
}
//consecutive series ended at the second last sub-array, write $temp and e pair to output and copy key value pair of the last sub-array to output
else if ($i==$arrCount-1){
$output[]=array('s'=> $temp, 'e' => $arr[$i]['e']);
$output[]=$arr[$arrCount];
}
//not in a consecutive series, simply copy s e key value pair to output
else {
$output[]=$arr[$i];
}
}//end of for loop
print_r($output);
}//end of function

else if ($i==$arrCount-1){ $output[]=!isset($temp) ? $arr[$i] : array('s'=> $temp, 'e' => $arr[$i]['e']); $output[]=$arr[$arrCount]; }

Related

PHP, Calculation Key in array

i have an array like this
Array
(
[0] => LK10110000
[1] => +
[2] => LK10120000
[3] => -
[4] => LK10130000
)
from that array I want to do a query based on the following sequence of array calculations
expected results :
Value = ResultMysql [LK10110000] + ResultMysql [LK10120000] - ResultMysql [LK10130000]`
From your question and the comments I understand you have a string that contains an expression and you need to run some queries and compute a result base on the expression. And the problem is that you don't know the expression in advance.
I assume your expression contains only addition and subtraction.
If it also contains multiplication or division, parentheses, functions or other operators then the rest of the answer does not apply, it needs more complex code to handle operators precedence, parentheses and function calls.
The idea
Initialize the variable holding the final result with 0.
Split the string into pieces as you already did.
Prepend a + sign to the array of pieces.
Take the first two pieces from the array. The first one is the sign, the second is the variable.
Compose and run the query using the variable as parameter.
Add or subtract the value returned by the query to the final result (check the sign retrieved on step 4 to know if it's add or subtract).
If there still are pieces not processed in the array, repeat from step 4.
The code
The code is less and more clear than the above description:
// This is the input expression
$expression = 'LK10110000; +; LK10120000; -; LK10130000';
// Step 1
$total = 0;
// Step 2
$pieces = explode(';', $pieces);
if (count($pieces) % 2 != 1) {
// The expression is incorrect; handle the situation somehow
//
// A valid expression must contain an odd number of items
// (alternating value and operator, starting and ending with a value)
}
// Extra processing: remove the padding spaces from around the values
// to ensure testing the sign against '-' works correctly
$pieces = array_map('trim', $pieces);
// Step 3
array_unshift($pieces, '+');
// Step 4
do {
$sign = array_unshift($pieces);
$value = array_unshift($pieces);
// Step 5
// ... use $value here to generate and run the query
// ... put the value returned by the query in variable $result
$result = 1; // <-- replace this line
// Step 6
if ($sign === '+') {
$total += $result;
} elseif ($sign === '-') {
$total -= $result;
} else {
// This is an error in the expression; handle it somehow
}
// Step 7
} while (count($pieces));
// The output is in $total
echo($total);
Remarks
If the queries do not return a single numeric value but a set of records ($value is an array of scalar, arrays or objects) then adjust the code on step 6 and use the appropriate merging of $value into $total. Also initialize $total with the correct array of scalars/arrays/objects.
The exact definition of "appropriate merging" depends on the rules of your application. To achieve it you probably must iterate over the elements of $value and for each one, find the corresponding element in $total and update it or insert it if not already present.

Error unset value from array in php

I want unset all value from this array, but result only remove half
Ex 1:
$filter = array("English", "Malay", "Student Pass", "NRIC");
for($i=0; $i<count($filter); $i++){
unset($filter[$i]);
}
print_r($filter);
=> Result Array ( [2] => Student Pass [3] => NRIC )
Ex 2:
$filter = array("English", "Malay");
for($i=0; $i<count($filter); $i++){
unset($filter[$i]);
}
print_r($filter);
=> Result Array ( [1] => Malay )
Your problem is that every time your for loop runs, it recalculates the array's length with count($filter). Thus, the for loop runs less often than there are elements within the array. Instead you should determine the array's length ahead of the loop and only use this variable within the loop:
e.g.:
<?
$filter = array("English", "Malay", "Student Pass", "NRIC");
$arrayLength = count($filter); // contains the initial length of the array
for($i=0; $i<$arrayLength;$i++) {
unset($filter[$i]);
}
print_r($filter);
?>
However, regarding overall performance it might be better to overwrite the array or even unset it.
e.g.:
// Overwrite it with an empty array
$filter = array();
// Unset it
unset($filter);
Its because when you unset the item on the array it removes it. So on the next iteration the count($filter) has now gone down. You could do as #Daan suggested and just set $filter = array(). However if you insisted on calling unset you should loop over the array in reverse order.
for($i = count($filter) - 1; $i >= 0; $i--) {
unset($filter[$i]);
}
Each time you unset an element, count($filter) decreases. Let me give you an example:
You have six apples. You are eating the n'th apple while n is smaller than the number of apples. So, you eat the 0'th apple, because 0 < 6. You have 5 apples. You eat the 1st apple, because 1 < 5. You have 4 apples. You eat the 2nd apple, because 2 < 4. You have 3 apples. You don't eat the 3rd apple, since 3 < 3 is not true. As a result, three apples will remain. Your mistake was assuming that the ever incremented index is slower than the ever decremented count if and only if the array is not empty. This is wrong and you need a different approach, like this:
while (count($filter) > 0) {
unset($filter[count($filter) - 1]);
}
This is sub-optimal though, just unset the array instead, like this:
unset($filter);

Check for coherency in an array?

I have an array with elements like this:
1_elem1
2_elem2
3_elem3
Now I want to check if something in that structure is out of place. Out of place means:
The numbers in front are not succeeding
Numbers are missing
Whats the shortest way to do that in PHP? I thought of sorting the array and check if each element starts with a number. That would solve 2. I am not sure how to do 1. though.
If you can solve the 2nd problem, then you could check if the number is equivalent to the index of the array, taking into account the number of missing numbers up to that point.
Meaning that, the index of each element should be equal to the number in front.
Except when there has been a number missing in the past elements, but if you take that into account, you could include a incrementor and subtract that value.
That's a kind of combination of both arrays and regular expressions.
<?php
$arr = array( // here is our array
"1_elem1",
"4_elem2", // here is an unexpected value
"3_elem3"
);
$arr = array_values($arr); // if our array has custom keys change it to consecutive numbers
try {
array_walk($arr, function($val, $key)
{
$key++; // add 1 to the index ( because starts at 0)
$pattern = "#".$key."_elem".$key."#"; // write down our pattern
if(!preg_match($pattern, $val))
{
throw new Exception("Not in sequence: ".$val, 1); // if not in sequence then throw an error
}
});
echo "Array has sequential values";
} catch (Exception $e) {
echo $e;
die();
}
Since you asked for "shortest", how about:
function check($array) {
for ($i = count($array)-1; 0 < $i; $i--)
if (1 !== ((int)$array[$i] - (int)$array[$i-1]))
return false;
return true;
}
Start at the end walking backward, compute difference between this and previous element, if not exactly one return false. Otherwise return true. Note the typecast to int is simple way to get leading digits.

create another multi dimensional array from an array

Suppose i have an array
$x= ('A'=>31, 'B'=>12, 'C'=>13, 'D'=>25, 'E'=>18, 'F'=>10);
I need to generate an array somewhat like this
$newx = (0 => array('A'=>31 , 'B' =>1) , 1 => array('B'=>11 , 'C' =>13 , 'D'=>8) , 2 =>array('D'=>17 , 'E'=>15) , 3=>array('E'=>3,'F'=>10);
Now in this case each value of $newx has to be = 32 and this is how it will work $x[A] = 31 , $x[B] = 12 so first of all we have to make the sum quantity to be 32 keeping the index same for the new array i.e
array(0=>array('A'=>31,'B'=>1) , 1=>array('B'=>11) )
the process should continue for each value of $x.
while I'm pretty sure this is a homework assignment and well, you really should provide code of your own, at least try to, I found the thing amusing so I went ahead and gave it a try. I guess I'll be downvoted for his and I probably do deserve it, but here goes anyway.
What you need to do is:
loop through your array,
determine the elements that give you 32 and then store that result in the final array.
subtract the value of the last element from your result from the corresponding element of your working array
shrink your array next by deleting the first elements until the very first element of the array you're still working with equals the last element your last result returned.
if your last result < 32, quit.
With this in mind, please try to find a solution yourself first and don't just copy-paste the code? :)
<?php
$x = array('A'=>31, 'B'=>12, 'C'=>13, 'D'=>25, 'E'=>18, 'F'=>10);
$result = array();
function calc($toWalk){
// walk through the array until we have gathered enough for 32, return result as an array
$result = array();
foreach($toWalk as $key => $value){
$count = array_sum($result);
if($count >= 32){
// if we have more than 32, subtract the overage from the last array element
$last = array_pop(array_keys($result));
$result[$last] -= ($count - 32);
return $result;
}
$result[$key] = $value;
}
return $result;
}
// logic match first element
$last = 'A';
// loop for as long as we have an array
while(count($x) > 0){
/*
we make sure that the first element matches the last element of the previously found array
so that if the last one went from A -> C we start at C and not at B
*/
$keys = array_keys($x);
if($last == $keys[0]){
// get the sub-array
$partial = calc($x);
// determine the last key used, it's our new starting point
$last = array_pop(array_keys($partial));
$result[] = $partial;
//subtract last (partial) value used from corresponding key in working array
$x[$last] -= $partial[$last];
if(array_sum($partial) < 32) break;
}
/*
reduce the array in size by 1, dropping the first element
should our resulting first element not match the previously returned
$last element then the logic will jump to this place again and
just cut off another element
*/
$x = array_slice($x , 1 );
}
print_r($result);

Peek ahead when iterating an array in PHP

Is it possible to "peek ahead" while iterating an array in PHP 5.2? For example, I often use foreach to manipulate data from an array:
foreach($array as $object) {
// do something
}
But I often need to peek at the next element while going through the array. I know I could use a for loop and reference the next item by it's index ($array[$i+1]), but it wouldn't work for associative arrays. Is there any elegant solution for my problem, perhaps involving SPL?
You can use the CachingIterator for this purpose.
Here is an example:
$collection = new CachingIterator(
new ArrayIterator(
array('Cat', 'Dog', 'Elephant', 'Tiger', 'Shark')));
The CachingIterator is always one step behind the inner iterator:
var_dump( $collection->current() ); // null
var_dump( $collection->getInnerIterator()->current() ); // Cat
Thus, when you do foreach over $collection, the current element of the inner ArrayIterator will be the next element already, allowing you to peek into it:
foreach($collection as $animal) {
echo "Current: $animal";
if($collection->hasNext()) {
echo " - Next:" . $collection->getInnerIterator()->current();
}
echo PHP_EOL;
}
Will output:
Current: Cat - Next:Dog
Current: Dog - Next:Elephant
Current: Elephant - Next:Tiger
Current: Tiger - Next:Shark
Current: Shark
For some reason I cannot explain, the CachingIterator will always try to convert the current element to string. If you want to iterate over an object collection and need to access properties an methods, pass CachingIterator::TOSTRING_USE_CURRENT as the second param to the constructor.
On a sidenote, the CachingIterator gets it's name from the ability to cache all the results it has iterated over so far. For this to work, you have to instantiate it with CachingIterator::FULL_CACHE and then you can fetch the cached results with getCache().
Use array_keys.
$keys = array_keys($array);
for ($i = 0; $i < count($keys); $i++) {
$cur = $array[$keys[$i]];
$next = $array[$keys[$i+1]];
}
You can use next and prev to iterate an array. current returns the current items value and key the current key.
So you could do something like this:
while (key($array) !== null) {
next($array); // set pointer to next element
if (key($array) === null) {
// end of array
} else {
$nextItem = current($array);
}
prev($array); // resetting the pointer to the current element
// …
next($array);
}
I know that this is an old post, but I can explain that current/next/prev thing better now.
Example:
$array = array(1,2,3,2,5);
foreach($array as $k => $v) {
// in foreach when looping the key() and current()
// is already pointing to the next record
// And now we can print current
print 'current key: '.$k.' and value: '.$v;
// if we have next we can print its information too (key+value)
if(current($array)) {
print ' - next key: '.key($array).' and value: '.current($array);
// at the end we must move pointer to next
next($array);
}
print '<br>';
}
// prints:
// current key: 0 and value: 1 - next key: 1 and value: 2
// current key: 1 and value: 2 - next key: 2 and value: 3
// current key: 2 and value: 3 - next key: 3 and value: 2
// current key: 3 and value: 2 - next key: 4 and value: 5
// current key: 4 and value: 5
I know I could use a for loop and reference the next item by its index ($array[$i+1]), but it wouldn't work for associative arrays.
Consider converting your associative array into an sequentially indexed one with array_values(), allowing you to use the simple for loop solution.
Old post but my two cents:
If you are trying to peek ahead, you really need to ask yourself "Am I solving this problem the best way possible."
You can solve all peek-ahead problems without ever doing a peek-ahead. All you need is a "$prevItem" reference declared before the collection and initialize it as null. Each time you go through the loop, at the end, set $prevItem to the current array item you just evaluated. Effectively, instead of peaking ahead, you start executing your real logic at the second item and use the $prevItem reference to do your operation. You skip the first item by noting that $prevItem is null.
$prevItem = null;
$prevKey = null;
foreach($collection as $key => $val)
{
if($prevItem != null)
{
//do your operation here
}
$prevItem = $val;
$prevKey = $key;
}
It's clean code and its a common pattern.
Stay away from poking around at underlying data structures while you are iterating through them... its never good practice, and extremely rare that you would need to do it.

Categories