Suppose you have an array "value => timestamp". The values are increasing with the time but they can be reset at any moment.
For example :
$array = array(
1 => 6000,
2 => 7000,
3 => 8000,
7 => 9000,
8 => 10000,
9 => 11000,
55 => 1000,
56 => 2000,
57 => 3000,
59 => 4000,
60 => 5000,
);
I would like to retrieve all the missing values from this array.
This example would return :
array(4,5,6,58)
I don't want all the values between 9 and 55 because 9 is newer than the other higher values.
In real condition the script will deal with thousands of values so it need to be efficient.
Thanks for your help!
UPDATE :
The initial array can be ordered by timestamps if it is easier for the algorithm.
UPDATE 2 :
In my example the values are UNIX timestamps so they would look more like this : 1285242603 but for readability reason I simplified it.
Here’s another solution:
$prev = null;
$missing = array();
foreach ($array as $curr => $value) {
if (!is_null($prev)) {
if ($curr > $prev+1 && $value > $array[$prev]) {
$missing = array_merge($missing, range($prev+1, $curr-1));
}
}
$prev = $curr;
}
You can do the following:
Keep comparing adjacent array keys. If
they are consecutive you do nothing.
If they are not consecutive then you
check their values, if the value has
dropped from a higher value to a
lower value, it means there was a
reset so you do nothing.
If the value has not dropped then it
is a case of missing key(s). All the
numbers between the two keys(extremes
not included) are part of the result.
Translated in code:
$array = array( 1 => 6000, 2 => 7000, 3 => 8000, 7 => 9000, 8 => 10000,
9 => 11000,55 => 1000, 56 => 2000, 57 => 3000, 59 => 4000,
60 => 5000,);
$keys = array_keys($array);
for($i=0;$i<count($array)-1;$i++) {
if($array[$keys[$i]] < $array[$keys[$i+1]] && ($keys[$i+1]-$keys[$i] != 1) ) {
print(implode(' ',range($keys[$i]+1,$keys[$i+1]-1)));
print "\n";
}
}
Working link
This gives the desired result array(4,5,6,58):
$previous_value = NULL;
$temp_store = array();
$missing = array();
$keys = array_keys($array);
for($i = min($keys); $i <= max($keys); $i++)
{
if(!array_key_exists($i, $array))
{
$temp_store[] = $i;
}
else
{
if($previous_value < $array[$i])
{
$missing = array_merge($missing, $temp_store);
}
$temp_store = array();
$previous_value = $array[$i];
}
}
var_dump($missing);
Or just use Gumbo's very smart solution ;-)
Related
I have a problem getting the min and max values from a row of numbers with a special difference of 1800 in it. For better understanding, I want to give you the following example:
array(0,1800,3600,5400,7200,12600,14400,16200,23400,25200);
The special difference for my row of numbers is 1800. When a number has an exactly difference of 1800 to the next number, they are numbers of the same row. If not, they are numbers of another row. So I need a function which will produce the following output for the array mentioned above:
Output 1: min value 0, max value 7200
Output 2: min value 12600, max value 16200
Output 3: min value 23400, max value 25200
I hope you understand my question. Sorry for my english and thanks in advance.
It's alway better to show some of the code you have tried so far.
However here is a very short code example I put together.
$lists = [];
$special = 1800;
$array = [0, 1800, 3600, 5400, 7200, 12600, 14400, 16200, 23400, 25200];
$currentList = [];
foreach ($array as $number) {
if (empty($currentList)) {
$currentList[] = $number;
} else {
$last =(end($currentList) + $special);
if ($number === $last) {
$currentList[] = $number;
} else {
$lists[] = $currentList;
$currentList = [$number];
}
}
}
$lists[] = $currentList;
var_dump($lists);
This will output the following array, which could be transformed in the output you want.
array (size=3) 0 =>
array (size=5)
0 => int 0
1 => int 1800
2 => int 3600
3 => int 5400
4 => int 7200 1 =>
array (size=3)
0 => int 12600
1 => int 14400
2 => int 16200 2 =>
array (size=2)
0 => int 23400
1 => int 25200
You can create a helper function to iterate your input and add the values to an array of rows
function getRows($input, $specialDifference) {
$rows = array();
$newRow = true;
for ($index = 0; $index < count($input); $index++) {
if ($newRow) {
$rows[] = array();
$newRow = false;
}
$rows[count($rows) - 1][]= $input[$index];
$newRow = ((count($input) > $index + 1) && ($input[$index + 1] - $input[$index] !== $specialDifference));
}
return $rows;
}
I have an array with 3 sub arrays like:
$array = array(
width => array(
0 => 1,
1 => 2,
2 => 3,
3 => 4,
4 => 5
),
height => array(
0 => 1,
1 => 2,
2 => 7,
3 => 8
),
color => array(
0 => 2,
1 => 7,
2 => 8
)
);
The count is in this case 3 as I have 3 arrays. This can be sometimes more or less, that's why I have a count.
Now I want to find out which number is in all 3 arrays and only use those. in above example the only returned number should be 2 as it's present in all 3 sub arrays.
I was trying something like below, but am really stuck what's the best approach...
$i = count($array); // gives me back the count of 3 which is correct
$n = 0;
foreach($array as $key=>values) {
foreach($values as $value) {
// do not how to proceed :(
}
}
You can call array_intersect with all the sub-arrays as arguments.
$common_values = call_user_func_array('array_intersect', $array);
Maybe you can affect your rows in some var and make what you want to do in your foreach
Like :
<?php
$width = 0;
$height = 0;
$color = 0 ;
$arrayBundleWidth = array();
$arrayBundleHeight = array();
$arrayBundleColor = array();
foreach ($array as $arrayRow){
$arrayBundleWidth = $arrayRow['width'];
$arrayBundleColor = $arrayRow['color'];
$arrayBundleHeight = $arrayRow['height'];
foreach ($arrayBundleWidth as $arrayBundleWidthtRow) {
# code...
#you got each index of your width array here
}
foreach ($arrayBundleHeight as $arrayBundleHeightRow) {
# code...
#you got each index of your height array here
}
foreach ($arrayBundleColor as $arrayBundleColorRow) {
# code...
#you got each index of your color array here
}
}
?>
Try it and tell me ? Is it what you want ?
Not sure if it's what you wanted to do !
I'm trying to create an algorithm that returns a price depending on number of hours. But the distance between the number of hours are varying. For example I have an array:
$set = [
1 => 0.5,
2 => 1,
3 => 1.5,
4 => 2,
5 => 2.5,
12 => 4
];
$value = 3;
end($set);
$limit = (int)key($set);
foreach($set as $v => $k) {
// WRONG, doesn't account for varying distance
if($value >= $v && $value <= $v) {
if($value <= $limit) {
return $k;
} else {
return false;
}
}
}
The trouble is, the distance between 5 and 12 register as null. I might as well use $value == $v instead as the line I've marked as incorrect does anyway.
So I was wondering if there was a better way to round up to the next index in that array and return the value for it?
Cheers in advance!
Try this:
$set = array(1 => 0.5, 2 => 1, 3 => 1.5, 4 => 2, 5 => 2.5, 12 => 4);
function whatever(idx, ary){
if(in_array(idx, array_keys(ary))){
return ary[idx];
}
else{
foreach(ary as $i => $v){
if($i > idx){
return $v;
}
}
}
return false;
}
echo whatever(7, $set);
The problem is that $v is a single value, so $value >= $v && $value <= $v is equivalent to $value == $v.
Instead, consider that if the loop hasn't ended, then the cutoff hasn't been reached yet - and a current "best price" is recorded. This requires that the keys are iterated in a well-ordered manner that can be stepped, but the logic can be updated for a descending order as well.
$price_chart = [
1 => 0.5,
2 => 1,
3 => 1.5,
4 => 2,
5 => 2.5,
12 => 4
];
function get_price ($hours) {
global $price_chart;
$best_price = 0;
foreach($price_chart as $min_hours => $price) {
if($hours >= $min_hours) {
// continue to next higher bracket, but remember the best price
// which is issued for this time bracket
$best_price = $price;
continue;
} else {
// "before" the next time cut-off, $hours < $min_hours
return $best_price;
}
}
// $hours > all $min_hours
return $best_price;
}
See the ideone demo. This code could also be updated to "fill in" the $price_chart, such that a price could be found simply by $price_chart[$hours] - but such is left as an exercise.
I have an array that is composed of information that looks like the following:
['Jay', 'Jay', 'Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'John', 'John', 'Dogs', 'Cows', 'Snakes']
What I'm trying to do is remove duplicate entries but only if they occur right next to each other.
The correct result should look like the following:
['Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'Dogs', 'Cows', 'Snakes']
I'm using PHP but any kind of logic would be able to help me out with this problem.
Here is some code I've tried so far:
$clean_pull = array();
$counter = 0;
$prev_value = NULL;
foreach($pull_list as $value) {
if ($counter == 0) {
$prev_value = $value;
$clean_pull[] = $value;
}
else {
if ($value != $pre_value) {
$pre_value = value;
}
}
echo $value . '<br>';
}
Francis, when I run the following code:
$lastval = end($pull_list);
for ($i=count($pull_list)-2; $i >= 0; $i--){
$thisval = $pull_list[$i];
if ($thisval===$lastval) {
unset($pull_list[$i]);
}
$lastval = $thisval;
}
# optional: reindex the array:
array_splice($pull_list, 0, 0);
var_export($pull_list);
, I get these results:
array ( 0 => 'NJ Lefler', 1 => 'Deadpool', 2 => 'NJ Lefler', 3 => 'Captain Universe: The Hero Who Could Be You', 4 => 'NJ Lefler', 5 => 'The Movement', 6 => 'NJ Lefler', 7 => 'The Dream Merchant', 8 => 'Nolan Lefler', 9 => 'Deadpool', 10 => 'Nolan Lefler', 11 => 'Captain Universe: The Hero Who Could Be You', 12 => 'Nolan Lefler', 13 => 'The Movement', 14 => 'Tom Smith', 15 => 'Deadpool', 16 => 'Tom Smith', 17 => 'Captain Universe: The Hero Who Could Be You', )
Your approach (a $prev_value variable) should work fine and you don't need a counter.
Your use of $counter is why your code doesn't work--the first half of the if statement is always executed because $counter is never incremented; and the second half just compares values. The only thing you need to do is compare the current value with the previous value and include the current value only if it differs (or remove it only if it's the same).
It's much easier to see this algorithm if you use functional reduction. Here is an example using array_reduce:
$a = array('Jay', 'Jay', 'Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'John', 'John', 'Dogs', 'Cows', 'Snakes');
$na = array_reduce($a, function($acc, $item){
if (end($acc)!==$item) {
$acc[] = $item;
}
return $acc;
}, array());
var_export($na);
Note this comparison of var_export($a) (your original array) and var_export($na) (the result produced by the code):
$a = array ( $na = array (
0 => 'Jay', 0 => 'Jay',
1 => 'Jay', 1 => 'Spiders',
2 => 'Jay', 2 => 'Dogs',
3 => 'Spiders', 3 => 'Cats',
4 => 'Dogs', 4 => 'John',
5 => 'Cats', 5 => 'Dogs',
6 => 'John', 6 => 'Cows',
7 => 'John', 7 => 'Snakes',
8 => 'John', )
9 => 'Dogs',
10 => 'Cows',
11 => 'Snakes',
)
The array_reduce() method does exactly the same thing as the following code:
$na = array();
foreach ($a as $item) {
if (end($na)!==$item) {
$na[] = $item;
}
}
Instead of returning a copy of an array, you can also modify the array in-place using the same algorithm but starting from the end of the array:
$lastval = end($a);
for ($i=count($a)-2; $i >= 0; $i--){
$thisval = $a[$i];
if ($thisval===$lastval) {
unset($a[$i]);
}
$lastval = $thisval;
}
# optional: reindex the array:
array_splice($a, 0, 0);
var_export($a);
Keep track of the last element in the array, and skip adding the next element to your new array if you just added it.
Or, you can just check the last element in the array, and see if it's not the current element in your array:
$array = ['Jay', 'Jay', 'Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'John', 'John', 'Dogs', 'Cows', 'Snakes'];
$new = array( array_shift( $array));
foreach( $array as $el) {
if( !($new[count($new) - 1] === $el)) {
$new[] = $el;
}
}
Assuming the array isn't so large that having a second one will cause a problem, the approach you described should work. Does it look like this?
$last = null;
$result = [];
foreach($arr as $item)
if($item !== $last)
$result[] = $last = $item;
Re: edit:
$pre_value and $prev_value aren't the same thing
$counter doesn't change
It looks like you tried to combine a counter approach and a "last" approach somehow.
Define a global variable glob.
pass the array:
if (array[i] == glob) then
remove array[i]
else
glob = array[i];
keep array[i];
I found this thread about picking the closest/nearest value from an array based upon a known value. What about if one wants to pick the two nearest values from an array looking at the same say?
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35)
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35);
function getArrayNeighborsByKey($array, $findKey) {
if ( ! array_key_exists($array, $findKey)) {
return FALSE;
}
$select = $prevous = $next = NULL;
foreach($array as $key => $value) {
$thisValue = array($key => $value);
if ($key === $findKey) {
$select = $thisValue;
continue;
}
if ($select !== NULL) {
$next = $thisValue;
break;
}
$previous = $thisValue;
}
return array(
'prev' => $previous,
'current' => $select,
'next' => $next
);
}
See it!
By "two nearest" you mean the two smaller than or equal to the value of $items?
Anyway, starting from the answer to that other thread, which is
$percent = $rebates[max(array_intersect(array_keys($rebates),range(0,$items)))];
You can go to
$two_nearest = array_slice(array_intersect(array_keys($rebates),range(0,$items)), -2);
$most_near = $rebates[$two_nearest[1]];
$less_near = $rebates[$two_nearest[0]];
This can probably be reduced to an one-liner using array_map, but I think it's overdone already.
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35)
$distances = array();
foreach($rebates as $key=>$item) {
if ($key == 5) continue;
$distances = abs($rebates[5] - $item);
}
sort($distances, SORT_NUMERIC)
Now you have an array with all the items in the array with their distance to $rebates[5] sorted. So you can get the two closest ones.
Or three closest ones. Whatever.
Just keep in mind that 2 items can have the same distance.