Peek ahead when iterating an array of arrays in PHP - php

I have a mysql query object result which I want to parse through. Let's say the array looks like this.
$superheroes = array(
[0] => array(
"name" => "Peter Parker",
"email" => "peterparker#mail.com",
"age"=>"33",
"sex"=>"male",
),
[1] => array(
"name" => "jLaw",
"email" => "jlaw#mail.com",
"age"=>"22",
"sex"=>"female",
),
[2] => array(
"name" => "Clark Kent",
"email" => "clarkkent#mail.com",
"age"=>"36",
"sex"=>"male",
),
[3] => array(
"name" => "Gal Gadot",
"email" => "gal#mail.com",
"age"=>"22",
"sex"=>"female",
)
);
I want to iterate through this array, and while I am at each array, I want to look ahead in the next array, and find out the age difference between current male hero and next immediate female in the list. I found a lot of posts that talk about
1. array_keys
2. caching iterators,
3. prev, next, etc.
But all of them are talking about one dimensional arrays. Here Is what I tried
foreach ($superheroes as $key => $list){
if($list['sex']=="male"){
$currentHerosAge=$list['age'];
while($next=next($superheroes)){
if($next['sex']=="female"){
$diff=$currentHerosAge -$next['age'];
echo "Age diff: ".$diff;
break;
}
}
}
}
But when I try this, for array[0], next misses the array[1], and picks up array[3]. Not sure how to work this out.

You can use array_slice to slice array form the next key index and then look for the fist female age
$diff = [];
foreach($superheroes as $key => $male) {
if ($male['sex'] === 'female' ) continue;
// get the next key index
$nextKey = $key + 1;
if ( isset( $superheroes[ $nextKey ] ) ) {
// slice from the next key index
$nextSuperHeros = array_slice($superheroes, $nextKey);
foreach($nextSuperHeros as $k => $female) {
if ($female['sex'] === 'female') {
$diff[] = $male['age'] - $female['age'];
break;
}
}
}
}
A working Example
Hope this helps

Normally, you don't use a "lookahead", when iterating over the array. The common way would be to store the item of the last iteration and do the comparison on this element
$last_hero = false;
foreach ($superheroes as $hero) {
if ($last_hero) {
// do some stuff..
}
$last_hero = $hero;
}
If you really need a lookahead of more than one item, you wouldn't use a foreach loop.
for ($i = 0; $i < count($superheroes); $i++) {
// do sth. with $superheroes[$i]
if (...) {
for ($j = $i + 1; $j < count($superheroes); $j++) {
// do sth. with $superheroes[$j]
}
}
}

this code works, here's your solution for that situation (i will update explaining why) + editing the Clark - Gal part, one more comparison.
Execute it here if you like. this is a fancy version with echo's just to illustrate the running logic and what is happening inside the loops :) of course the final result can be much more simple.
<?php
$superheroes = [
[
"name" = "Peter Parker",
"email" = "peterparker#mail.com",
"age"="33",
"sex"="male",
],[
"name" = "jLaw",
"email" = "jlaw#mail.com",
"age"="22",
"sex"="female",
],[
"name" = "Clark Kent",
"email" = "clarkkent#mail.com",
"age"="36",
"sex"="male",
],[
"name" = "Gal Gadot",
"email" = "gal#mail.com",
"age"="22",
"sex"="female",
]
];
$length = count($superheroes);
$counterReset = 0;
while ($current = current($superheroes) )
{
$length -= 1;
if($current['sex']=="male"){
$currentHerosAge = $current['age'];
while($next = next($superheroes)){
$counterReset += 1;
if($next['sex']=="female"){
$diff=$currentHerosAge - $next['age'];
echo "\n C: ".$current['name']." to ".$next['name']." Age diff: ".$diff."\n";
break;
}
}
}
if($counterReset 0){
for($i = 0; $i < $counterReset; $i++){
prev($superheroes);
}
$counterReset = 0;
}
if($length == 0){
break;
}
next($superheroes);
}
current() next() prev() all act as pointers. Which means that every time you call them you control the movement of the assuming "head" of array or element you are calling. For this reason every time you move with next() you must make sure you also return back to the desired position in order to continue looping the regular loop :)
References next(), current(), prev() and reset()
execution logic
You declaration of the array or arrays was bringing a PHP Warning PHP Warning: Illegal offset type in /home/ on line 21 and was empty. For that reason I re-declared that way.

Related

Cluster PHP array

Let's say I have an array of items with each item a value. I'd like to
create a new array where the items are clustered by their relative distance to each other.
When an item has a distance of one to another item, they belong to each other.
$input = [
'item-a' => 1,
'item-b' => 2,
'item-c' => 3,
'item-d' => 5,
];
$output = [
['item-a', 'item-b'],
['item-b', 'item-c'],
['item-d'],
];
This will create an output of overlapping arrays. What I want is that, because item-a and item-b are related, and item-b is also
related to item-c, I'd like to group item-a, item-b, and item-c to each other. The distance to item-c and item-d is greater than
1 so it will for a cluster of itself.
$output = [
['item-a', 'item-b', 'item-c'],
['item-d'],
];
How do I even start coding this?
Thanks in advance and have a nice day!
This can only be tested in your environment but here is what it does
it attempts to find relative distances based on array index 0's hash
it resorts the input array by distances (assuming that in this stage some will be positive and some negative) - that gives us the info to put the hash array in an order
Take this new array and put the hash back in
build a final output array measuring distances and sorting the level of output array by a threshhold.
I put in a couple dummy functions to return distances, obviously replace with your own. This might need tweaking but at this point, it's in your hands.
<?php
// example code
$input = [
'item-a' => 'a234234d',
'item-f' => 'h234234e',
'item-h' => 'e234234f',
'item-b' => 'f234234g',
'item-m' => 'd234234j',
'item-d' => 'm234234s',
'item-e' => 'n234234d',
'item-r' => 's234234g',
'item-g' => 'f234234f',
];
function getDistanceFrom($from, $to) {
return rand(-3,3);
}
function getDistanceFrom2($from, $to) {
return rand(0,7);
}
// first sort by relative distance from the first one
$tmp = [];
$ctr = 0;
foreach ($input as $item => $hash) {
if ($ctr === 0) { $ctr ++; continue; }
$tmp[$item]=getDistanceFrom(reset($input), $hash);
}
uasort($tmp, function ($a, $b)
{
return ($a < $b) ? -1 : 1;
});
//now they're in order, ditch the relative distance and put the hash back in
$sortedinput = [];
foreach ($tmp as $item => $d) {
$sortedinput[$item] = $input[$item];
}
$output=[];
$last=0;
$level=0;
$thresh = 3; // if item is within 3 of the previous, group
foreach($sortedinput as $v=>$i) {
$distance = getDistanceFrom2($last, $i);
if (abs($distance) > $thresh) $level++;
$output[$level][]=array("item" => $v, "distance" => $distance, "hash" => $i);
$last = $i;
}
print_r($output);

Concatenating multiple arrays to be inserted into a mongo aggregation query using php

I am trying to create an aggregation query for mongo within php. However I need to dynamically create the unwind operations. I am trying to do this within a for loop shown below.
for($j = 0; $j < $step; $j++) {
if($j == 0) {
$compare = '$steps'; //On first loop, instantiate the first level to unwind
} else {
$compare = $compare.".steps"; //Concatenate every next level to unwind
}
$wind = $wind.array('$unwind' => $compare); //Need a way to concatenate arrays
}
As you can see, I have tried to concatenate the arrays on every iteration, but this does not work.
Is there a way I can automate the creation of a variable containing the arrays below, depending on a number given for the number of unwinds needed - in the example below, the number is 3.
$wind = array('$unwind' => '$steps'), array('$unwind' => '$steps.steps'), array('$unwind' => '$steps.steps.steps'); //What I want end variable output to be
Final desired aggregation query below.
$query = array(
array(
'$match' => array(
"name" => $process['name'], $query => $title
)
),
$wind,
array(
'$project' => array(
"_id" => 0,
"steps.title" => 1,
)
),
);
EDIT: Solved, I used array_push to insert each part into the array. Seems I was making the problem way too complicated for myself.
You need array_merge. This function will merge your arrays. Example:
$wind = array();
for($j = 0; $j < $step; $j++) {
if($j == 0) {
$compare = '$steps'; //On first loop, instantiate the first level to unwind
} else {
$compare = $compare.".steps"; //Concatenate every next level to unwind
}
$wind = array_merge($wind, array('$unwind' => $compare)); //Need a way to concatenate arrays
}
Code is untested, let me know if there is any problem.

Given an array, find zero values and replace them with the average of immediately bordering values

I have an array of temperature data by hour. Some hours have zero data instead of a temp. When graphing using Google Charts, the zero causes the line graph to plummet. My temporary fix was to replace the zero values with null, causing a break in the line graph. The ideal solution would be to take the values on either side of the zero, and average them. The array is in order by hour. Help?
$array = array(
"1AM" => "65",
"2AM" => "66",
"3AM" => "68",
"4AM" => "68",
"5AM" => "68",
"6AM" => "0",
"7AM" => "70",
"8AM" => "71",
"9AM" => "71",
"10AM" => "73",
);
Here's my script replacing the 0's with nulls:
$array = array ();
foreach($parsed_json->history->observations as $key => $value) {
$temp = (int)$value->tempi;
if ($temp==0) {
str_replace(0, null, $temp);
}
$hour = $value->date->hour;
$array[$hour] = $temp;
};
This Example would work great if the data was mine, but alas, it's from a JSON feed.
Would I use an array_walk() sort of deal? How would I reference the current place in the array? Any help is appreciated!
I would scratch out the null portion, and just foreach-loop through the final array.
So, change your current code to:
$array = array ();
foreach($parsed_json->history->observations as $key => $value) {
$temp = (int)$value->tempi;
}
$hour = $value->date->hour;
$array[$hour] = $temp;
And add this below it:
foreach($array as $hour => $temp){
if($temp == "0"){
$numHour = $hour[0];
$hourPlus = ($numHour + 1) . "AM";
$hourMinus = ($numHour - 1) . "AM";
$valuePlus = $array[$hourPlus];
$valueMinus = $array[$hourMinus];
$average = ($valuePlus + $valueMinus)/2;
$array[$hour] = $average;
}
}
?>
This of course assumes that the values on either side of the zero are also not zero. You may want to add a check for that somewhere in there.
Tested and proven method.
Couldn't you do something along the lines of:
str_replace(0, ((($key-1)+($key+1))/2), $temp);
Where $key is array position, take the value before 0 and after 0 add them and divide them by 2 to get the average.
I'll let you sort out what happens if first, last or consecutive values are 0.
$the_keys=array_keys($array);
foreach($the_key as $index=>$key)
{
if($array[$key]==0)
{
$array[$key]=($array[$the_key[$index-1]]+$array[$the_key[$index+1]]/2);
}
}

Calculate the highest duplicate integers of two associative arrays

Given 2 associative arrays:
$fruits = array ( "d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple" );
$fruits2 = array ( "e" => "lemon", "f" => "apple", "g" => "melon", "h" => "apple" );
I would like to do something like:
for ( $n = count($fruits), $i = 0; $i < $n; $i++)
{
$test = (bool) $fruits[$i] == $fruits2[$i];
}
This can not work as I am using associative array. What would be the best way to go to achieve that? (This loops is going to be ran intensity so I would like to keep it as light as possible)
EDIT to give more detail on what I am trying to do:
Here is a better example of what I am trying to achieve:
$array = array ( 1,2,3,4,3,2 );
$array2 = array ( 9,6,3,4,3,2 );
$counts = array_count_values( $words );
$counts2 = array_count_values( $words2 );
Given the arrays above I need to calculate which array as the highest duplicate integers. Imagine a poker game, comparing two hands that each contain duplicate cards, how to evaluate which set of duplicate (whether double, triple or quadruple ) as the highest value.
Use array array_values ( array $input ) function and compare them.
$value1=array_values($fruits);
$value2=array_values($fruits2);
for ( $i = 0; $i < count($value1); $i++)
{
$test[] = $value1[$i] == $value2[$i] ? TRUE : FLASE;
}
Got it working this way :
$n = count($fruits);
for ( $i = 0; $i < $n; $i++)
{
$cur_vals_1 = each ($fruits);
$cur_vals_2 = each ($fruits2);
$sum1 += $cur_vals_1['key'] * $cur_vals_1['value'];
...
}
You're probably chasing the wrong solution. To find the highest duplicate in an array I'd use this (PHP 5.3+ syntax):
max(array_keys(array_filter(array_count_values($array), function ($i) { return $i >= 2; })))
Do this for both arrays and compare which result is higher. Trying to compare both against each other at the same time is too convoluted.
You don't need the ternary operator.
Also, be careful with count() as it will fail if your array is null.
They also need to be equal lengths or you'll get an error.
if( is_array($value1) && is_array($value2) && count($value1)==count($value2) ) {
for ( $i = 0; $i < count($value1); $i++)
{
$test[] = ($value1[$i] == $value2[$i]);
}
}

Replace non-specified array values with 0

I want to replace all array values with 0 except work and home.
Input:
$array = ['work', 'homework', 'home', 'sky', 'door']
My coding attempt:
$a = str_replace("work", "0", $array);
Expected output:
['work', 0, 'home', 0, 0]
Also my input data is coming from a user submission and the amount of array elements may be very large.
A bit more elegant and shorter solution.
$aArray = array('work','home','sky','door');
foreach($aArray as &$sValue)
{
if ( $sValue!='work' && $sValue!='home' ) $sValue=0;
}
The & operator is a pointer to the particular original string in the array. (instead of a copy of that string)
You can that way assign a new value to the string in the array. The only thing you may not do is anything that may disturb the order in the array, like unset() or key manipulation.
The resulting array of the example above will be
$aArray = array('work','home', 0, 0)
A loop will perform a series of actions many times. So, for each element in your array, you would check if it is equal to the one you want to change and if it is, change it. Also be sure to put quote marks around your strings
//Setup the array of string
$asting = array('work','home','sky','door')
/**
Loop over the array of strings with a counter $i,
Continue doing this until it hits the last element in the array
which will be at count($asting)
*/
for($i = 0; $i < count($asting);$i++){
//Check if the value at the 'ith' element in the array is the one you want to change
//if it is, set the ith element to 0
if ($asting[$i] == 'work' || $asting[$i] == 'home')
$asting[$i] = 0;
}
Here is some suggested reading:
http://www.php.net/manual/en/language.types.array.php
http://www.php.net/manual/en/language.control-structures.php
But if you are struggling on stuff such as looping, you may want to read some introductory programming material. Which should help you really understand what's going on.
A bit other and much quicker way, but true, need a loop:
//Setup the array of string
$asting = array('bar', 'market', 'work', 'home', 'sky', 'door');
//Setup the array of replacings
$replace = array('home', 'work');
//Loop them through str_replace() replacing with 0 or any other value...
foreach ($replace as $val) $asting = str_replace($val, 0, $asting);
//See what results brings:
print_r ($asting);
Will output:
Array
(
[0] => bar
[1] => market
[2] => 0
[3] => 0
[4] => sky
[5] => door
)
An alternative using array_map:
$original = array('work','home','sky','door');
$mapped = array_map(function($i){
$exclude = array('work','home');
return in_array($i, $exclude) ? 0 : $i;
}, $original);
you may try array_walk function:
function zeros(&$value)
{
if ($value != 'home' && $value != 'work'){$value = 0;}
}
$asting = array('work','home','sky','door','march');
array_walk($asting, 'zeros');
print_r($asting);
You can also give array as a parameter 1 and 2 on str_replace...
Just a small point to the for loop. Many dont realize the second comparing task is done every new iteration. So if it was a case of big array or calculation you could optimize loop a bit by doing:
for ($i = 0, $c = count($asting); $i < $c; $i++) {...}
You may also want to see http://php.net/manual/en/function.array-replace.php for original problem unless the code really is final :)
Try This
$your_array = array('work','home','sky','door');
$rep = array('home', 'work');
foreach($rep as $key=>$val){
$key = array_search($val, $your_array);
$your_array[$key] = 0;
}
print_r($your_array);
There are a few techniques on this page that make zero iterated function calls -- which is good performance-wise. For best maintainability, I recommend separating your list of targeted string as a lookup array. By modifying the original array values by reference, you can swiftly replace whole strings and null coalesce non-targeted values to 0.
Code: (Demo)
$array = ['work', 'homework', 'home', 'sky', 'door'];
$keep = ['work', 'home'];
$lookup = array_combine($keep, $keep);
foreach ($array as &$v) {
$v = $lookup[$v] ?? 0;
}
var_export($array);
Output:
array (
0 => 'work',
1 => 0,
2 => 'home',
3 => 0,
4 => 0,
)
You can very easily, cleanly extend your list of targeted strings by merely extending $keep.
If you don't want a classic loop, you can use the same technique without modifying the original array. (Demo)
var_export(
array_map(fn($v) => $lookup[$v] ?? 0, $array)
);
this my final code
//Setup the array of string
$asting = array('work','home','sky','door','march');
/**
Loop over the array of strings with a counter $i,
Continue doing this until it hits the last element in the array
which will be at count($asting)
*/
for($i = 0; $i < count($asting); $i++) {
//Check if the value at the 'ith' element in the array is the one you want to change
//if it is, set the ith element to 0
if ($asting[$i] == 'work') {
$asting[$i] = 20;
} elseif($asting[$i] == 'home'){
$asting[$i] = 30;
}else{
$asting[$i] = 0;
}
echo $asting[$i]."<br><br>";
$total += $asting[$i];
}
echo $total;

Categories