Finding the array name of a value closest to a variable? - php

I am attempting to find the closest product to the given budget
$array = array(
'productname1' => 5,
'productname2' => 10,
'productname3' => 15
)
$budget = 12;
I have tried using a function like the following to find the nearest value, but it only returns the number which is closest to the budget rather than the product name.
function closest($array, $number) {
sort($array);
foreach ($array as $a) {
if ($a >= $number) return $a;
}
return end($array);
}
I can't help but think there is a MUCH better implementation of this. Any help would be much appreciated.

foreach($array as $k => $v){ $diff[abs($v - $budget)] = $k; }
ksort($diff, SORT_NUMERIC);
$closest_key = current($diff);
var_dump($closest_key); // Product Name
var_dump($array[$closest_key]); // Product Cost
Prints:
string(12) "productname2"
int(10)
Or as a function:
function closest($array, $price)
{
foreach($array as $k => $v){ $diff[abs($v - $price)] = $k; }
ksort($diff, SORT_NUMERIC);
$closest_key = current($diff);
return array($closest_key, $array[$closest_key]);
}
print_r(closest($array, $budget));
Prints:
Array
(
[0] => productname2 // Product Name
[1] => 10 // Product Price
)
Both formats include only three steps:
Calculate the difference between the product cost and the budget
Sort these
Take the first element from the sorted array (the element whose price is closest to the budget).
EDIT: If you don't care about anything other than the single closest product, then a sort is overkill and a simple min() function (like Emil used) would be a lot faster. For example:
function closest($array, $price)
{
foreach($array as $k => $v){ $diff[abs($v - $price)] = $k; }
$closest_key = $diff[min(array_keys($diff))];
return array($closest_key, $array[$closest_key]);
}

function closest($array, $number) {
sort($array);
foreach ($array as $name => $a) {
if ($a >= $number) return $name;
}
return end(array_keys($array));
}
The trick comes in on this line:
foreach ($array as $name => $a) {
Here you assign $name to the array key and $a to the array value. Since you want the name, return $name;
Also, if no match is found, do, end(array_keys($array))); to get the name of the product, otherwise it will just spit out the value, which is not what you want.

You'll want to return the KEY, not the value:
function closest($array, $number) {
sort($array);
foreach ($array as $product=>$a) {
if ($a >= $number) return $product;
}
return $product;
}

Here's a functional way of doing it.
Map each post to the difference from the budget.
Find the smallest value.
Filter away all products not adhering to that value.
Implementation:
$diffs = array_map(function ($value) use ($budget) {
return abs($value - $budget);
}, $array);
$smallest = min($diffs);
$products = array_filter($array,
function ($value) use ($budget, $smallest) {
return abs($value - $budget) == $smallest;
});
$products will now contain all the products which are closest to the budget.

Related

Round number up to closest value in array

I've created an array with 'maximum' values in.
$management = array(
'100' => '1',
'500' => '2',
'1000' => '3',
);
And created a loop to find the closest value, rounding it up.
$speed = 10;
$r= '';
sort($management);
foreach($management as $mgmt => $name) {
if($mgmt >= $speed) {
$r= $name;
}
}
$r= end($management);
So, where the $speed is 10, it should pick up the array key 100 and if it was 100 it should still pickup 100 but if the speed was 200, it would pickup 500
The above is picking up 500 when the $speed is 10 though.
Can anyone help please?
You have a couple of problems with your code. Firstly, the call to sort rewrites all the keys of the $management array which you are using for the comparison; you need to use ksort instead to sort by the keys instead of the values. Secondly, since the keys are in ascending order, once one is greater than the $speed value, they all will be, so you need to break from the loop once you find a higher value. Try this instead:
$r= '';
ksort($management);
foreach($management as $mgmt => $name) {
if($mgmt >= $speed) {
$r= $name;
break;
}
}
echo $r;
Demo on 3v4l.org
this is an example on how you can do it :
$array = array(1, 10, 100, 200, 400, 500, 1000);
public function getArrayRoundUp($array, $number) {
sort($array);
foreach ($array as $a) {
if ($a >= $number) return $a;
}
return end($array);
}
$value = 950;
$nearest = getArrayRoundUp($array, $value);
//the expect result will be 1000
echo $nearest;
Use the following function
function find(array $array, $search)
{
$last = null; // return value if $array array is empty
foreach ($array as $key => $value) {
if ($key >= $search) {
return $key; // found it, return quickly
}
$last = $key; // keep the last key thus far
}
return $last;
}
Tested and Working:-
echo find($management, 100); // Will result 100
echo find($management, 200); //Will result 500

How To get sub array keys from array by less than values

I want to get sub array keys from array by less than values.
This is an example:
$arr_less_than = array(55,60,10,70);
$BlackList = array(10,8,15,20);
$MasterArray = array(
10 => array(1 => array(50,20,5,40), 2 => array(70,77,58,10), 3 => array(155,95,110,105), 4 => array(250,215,248,188)),
11 => array(1 => array(5,65,49,100), 2 => array(80,85,60,30), 3 => array(175,85,95,120), 4 => array(235,205,218,284)),
12 => array(1 => array(82,80,55,80), 2 => array(90,90,74,110), 3 => array(180,122,156,222), 4 => array(255,225,233,263)),
13 => array(1 => array(350,360,400,375), 2 => array(95,99,111,75), 3 => array(188,112,66,111), 4 => array(66,69,33,110)),
);
Now I need to get sub array keys from $MasterArray by less than $arr_less_than if the sub array key is not in the array $BlackList.
For the above example, the result must return array(12,13).
Note: I don't want to use a foreach loop
There are 2 solutions here - one for all sub-arrays meet the criteria, and one for any sub-array meets the criteria (which it seems is what the OP had in mind). At the end, there are foreach based solutions for the latter case where ANY sub-array meets the criteria.
If I understand the problem correctly, the goal is to identify rows in MasterArray where all of the sub-arrays have values that are greater than the corresponding value in $arr_less_than.
The OP does not want to use foreach (See the end of the answer for foreach based answers which are much simpler) - which may actually produce a more efficient version because it can avoid unnecessary compares and save some cycles, so here is a heavily annotated version using array functions.
I've excluded the data which can be copied from OP's post:
function getMatchingRows($arr_less_than, $MasterArray, $BlackList)
{
return array_values( // When we're done we just want a straight array of the keys from $MasterArray
array_filter(
array_keys($MasterArray), // Iterate over $MasterArray's keys (Because we need the keys for the BlackList)
function ($v) use ($BlackList, $MasterArray, $arr_less_than) {
// Filter out MasterArray Entries that dont meet the criteria
echo "Evaluate $MasterArray[$v]" . PHP_EOL;
// Remove array entries whose key is in the BlackList
if (in_array($v, $BlackList)) {
echo "\tBlacklisted" . PHP_EOL;
return false;
}
// For each entry in the MasterArray value, add up the number of non-matching entries
$y = array_reduce(
$MasterArray[$v],
function ($c1, $sub) use ($arr_less_than) {
// For each subarray entry in a MasterArray value, reduce the array to a count
// of elements whose value is less than the corresponding value in the $arr_less_than
$s1 = array_reduce(
array_keys($sub),
function ($carry, $key) use ($sub, $arr_less_than) {
if ($sub[$key] <= $arr_less_than[$key]) {
return ++$carry;
}
},
0 // Initial value for the array_reduce method
);
// $s1 will be a count of non-matching values
return $c1 + $s1;
},
0 //Initial value for the array_reduce method
);
echo "\t$y" . PHP_EOL;
// Include the array value in the filter only if there are no non-matching values ($y == 0)
return !$y;
}
)
);
}
print_r(getMatchingRows($arr_less_than, $MasterArray, $BlackList));
The basic idea is to generate a list of keys from the outermost array - so we iterate over them with array_filter. Then we exclude those with a key in the blacklist. Rows that arent in the blacklist, are reduced into an integer by iterating over each sub=arrays values and comparing them positon-wise against $arr_less_than and adding 1 for each value that fails to be greater than the corresponding member in $arr_less_than. Then those values are summed for all of the members in the MasterArray row. If the result is zero, then the row passes. Finally, the ultimate result is passed to array_values to normalize the resulting array.
Note that this requires that all values be compared, even if the first sub-value in the first sub-array fails. For that reason a foreach approach that can escape may be more efficient.
This is essentially the same method without comments and couple of shortcuts:
function getMatchingRows($arr_less_than, $MasterArray, $BlackList)
{
return array_values( // When we're done we just want a straight array of the keys from $MasterArray
array_filter(
array_keys($MasterArray),
function ($v) use ($BlackList, $MasterArray, $arr_less_than) {
return !in_array($v, $BlackList) && !array_reduce(
$MasterArray[$v],
function ($c1, $sub) use ($arr_less_than) {
return $c1 ?: array_reduce(
array_keys($sub),
function ($carry, $key) use ($sub, $arr_less_than) {
return $carry ?: ($sub[$key] <= $arr_less_than[$key] ? 1 : 0);
},
0
);
},
0
);
}
)
);
}
Some of the methods in array_reduce are short-circuited using ?: operator since the actual count is irrelevant. Once the count exceeds zero, the row fails, regardless.
Here is similar code if the criterion is that AT LEAST ONE sub-array has all members greater than the reference array.
function getMatchingRowsAny($arr_less_than, $MasterArray, $BlackList)
{
return array_values( // When we're done we just want a straight array of the keys from $MasterArray
array_filter(
array_keys($MasterArray), // Iterate over $MastrArray's keys (Because we need the keys for theBlackList)
function ($v) use ($BlackList, $MasterArray, $arr_less_than) {
// Filter out MasterArray Entries that dont meet the criteria
echo "Evaluate \MasterArray[$v]" . PHP_EOL;
// Remove array entries whose key is in the BlackList
if (in_array($v, $BlackList)) {
echo "\tBlacklisted" . PHP_EOL;
return false;
}
// For each entry in the MasterArray value, add up the number of non-matching entries
$y = array_reduce(
$MasterArray[$v],
function ($c1, $sub) use ($arr_less_than) {
// For each subarray entry in a MasterArray value, reduce the array to a flag
// indicating if it has whose value is <= the corresponding value in the $arr_less_than
$s1 = array_reduce(
array_keys($sub),
function ($fail, $key) use ($sub, $arr_less_than) {
return $fail || $sub[$key] <= $arr_less_than[$key];
},
false
);
// This could be short-circuited above to avoid an unnecessary array_reduce call
return $c1 || !$s1;
},
false
);
echo "\t$y" . PHP_EOL;
// Include the array value in the filter if there are any matching values
return $y;
}
)
);
}
print_r(getMatchingRowsAny($arr_less_than, $MasterArray, $BlackList));
As an exercise (and because I'm a glutton for punishment) I rendered the same methods using foreach as both a generator and a function returning an array - mostly to illustrate that foreach may be the better choice, and is definitely simpler:
// Implemented as a generator - The associated foreach that uses it follows
function generateMatchingRows($arr_less_than, $MasterArray, $BlackList)
{
foreach ($MasterArray as $k => $v) {
if (in_array($k, $BlackList)) {
continue;
}
foreach ($v as $sub_array) {
$match = true;
foreach ($sub_array as $k1 => $v1) {
if ($v1 <= $arr_less_than[$k1]) {
$match = false;
break;
}
}
if ($match) {
yield $k;
break;
}
}
}
}
foreach (generateMatchingRows($arr_less_than, $MasterArray, $BlackList) as $k) {
echo $k . PHP_EOL; // Or push them onto an array
}
// Implemented as a function returning an array - classical approach - just return an array
function getMatchingRowsForEach($arr_less_than, $MasterArray, $BlackList)
{
$rv = [];
foreach ($MasterArray as $k => $v) {
if (in_array($k, $BlackList)) {
continue;
}
foreach ($v as $sub_array) {
$match = true;
foreach ($sub_array as $k1 => $v1) {
if ($v1 <= $arr_less_than[$k1]) {
$match = false;
break;
}
}
if ($match) {
$rv[] = $k;
break;
}
}
}
return $rv;
}
print_r(getMatchingRowsForEach($arr_less_than, $MasterArray, $BlackList));

How can I add certain values of an array together and then sort by the sum?

So I am trying to find the best model within our database to match the user's inputted values for Height, Weight, etc. I have generated a variable called heightMatchMultiple based on weighted values and stored all the results in a global array $models. How can I apply math to only the ____MatchMultiple fields and then sort by the result? For example, I store the modelID of each model but I only want to add up the other values and then find the highest average.
Here is some relevant code:
while($row=mysqli_fetch_array($array))
{
$userHeight=$_POST['height'];
$userWeight=$_POST['weight'];
$userShoulder=$_POST['shoulder'];
$userWaist=$_POST['waist'];
$userInseam=$_POST['inseam'];
$heightMatchMultiple=0;
//creates a weighted variable for height
if(isset($row['modelHeight']))
{
if($userHeight==$row['modelHeight'])
{
$heightMatchMultiple=10;
}
elseif($userHeight==($row['modelHeight']+1) || $userHeight==($row['modelHeight']-1))
{
$heightMatchMultiple=9;
}
//same code down til multiple hits 1
else
{
$heightMatchMultiple=1;
}
//similar code for the weight, shoulders, etc.......
//....
array_push($models,array("modelID" => $row['modelID'],
"modelHeightMatch" => $heightMatchMultiple,
"modelWeightMatch" => $weightMatchMultiple,
"modelShoulderMatch" => $shoulderMatchMultiple,
"modelWaistMatch" => $waistMatchMultiple,
"modelInseamMatch" => $inseamMatchMultiple));
I would like to make a function that adds all of the _____MatchMultiple variables within the array (not including modelID) and then divides by the number of items within the array for each. This way I can find out which model is the closest match.
function getAverage($array) {
var $myTotal = 0
for ($i = 1; $i<= sizeof($array) - 1; $i++) {
$myTotal += $array[$i]
}
var $myAverage = $myTotal/(sizeof(array) - 1)
return($myAverage)
}
Does this sum_array() function solve it for you?
function safe_key_inc(&$arr, $key, $inc)
{
if (array_key_exists($key, $arr))
$arr[$key] += $inc;
else
$arr[$key] = $inc;
}
function sum_array($arr)
{
$sum_arr = [];
foreach ($arr as $el)
foreach ($el as $key => $val)
{
if (strstr($key, "Match"))
safe_key_inc($sum_arr,$key,$val);
}
return $sum_arr;
}

Finding the higest value in a loop

I am trying to return the highest percentage value along with all the other information like name,average, percentage, color etc in the array.
foreach ($array as $value) {
$name = getName($value);
$average = getAverage($value);
$percentage = getPercentage($value);
$color = getColor($value);
return $percentage;
}
How can i implement the solution to find the desired value (highest percentage) and return it If an array. I am thinking sorting can be one way but i am still not clear how should i do it.
Try this
$percentage1=0;
$datas=array();
foreach ($array as $value) {
$name = getName($value);
$average = getAverage($value);
$percentage = getPercentage($value);
$color = getColor($value);
if($percentage>$percentage1)
{
$percentage1=$percentage;
$datas['name']=$name;
$datas['average']=$average;
$datas['percentage']=$percentage;
$datas['color']=$color;
}
return $datas;
}
Though the code in your question doesn't make any sense, you can use php max function to get highest value if all value in array are numbers.
For example:
<?php $highest_value = max($array); ?>
If you want to use the buit-in PHP sorting, you can use usort(). It use a custom function to sort your array.
usort($array, function($a, $b){
$percentage_a = getPercentage($a);
$percentage_b = getPercentage($b);
if($percentage_a === $percentage_b) {
return 0;
}
return ($percentage_a > $percentage_b)? -1 : 1;
});
return array(
"name" => getName($array[0]),
"average" => getAverage($array[0]),
"percentage" => getPercentage($array[0]),
"color" = getColor($array[0]));
Then, your array is sorted with the highest percentage first.
If you don't want anonymous function, you can juste define it and call usort with its name :
function getMaxPercentage($a, $b){
$percentage_a = getPercentage($a);
$percentage_b = getPercentage($b);
if($percentage_a === $percentage_b) {
return 0;
}
return ($percentage_a > $percentage_b)? -1 : 1;
}
usort($array, 'getMaxPercentage');
return return array(
"name" => getName($array[0]),
"average" => getAverage($array[0]),
"percentage" => getPercentage($array[0]),
"color" = getColor($array[0]));

Knowning at which point we are in a foreach

I have an array:
$array=array('key1'=>'value1','key2'=>'value2','value3');
and a foreach:
foreach($array as $v){
//do something
}
Is there a way to know in the foreach which element we are parsing?
(without doing something like:)
$count=0;
foreach($array as $v){
$count++;
// do something
}
thanks
EDIT1:
Sorry maybe I was not clear:
I don't want know the key, but I need to know how many elements are left in the foreach. (that's why I did the example with $count)
You could use a class that extends ArrayIterator:
class FooArrayIterator extends ArrayIterator {
private $offset = 0;
public function next() {
$this->offset++;
return parent::next();
}
public function rewind() {
$this->offset = 0;
parent::rewind();
}
public function seek($offset) {
if ($offset >= 0 && $offset < $this->count()) {
$this->offset = $offset;
}
parent::seek($offset);
}
public function offset() {
return $this->offset;
}
}
Example:
$array = array('value1','value2','value3');
$iter = new FooArrayIterator($array);
foreach ($iter as $v) {
echo ($iter->count() - $iter->offset() - 1).' more';
}
You can get the actual index:
foreach ($array as $index => $value) {
}
If you are working with an associative array there is no way to tell the current position of the internal array pointer. There is only an indirect way: you search for the current key in the keys of the array with:
foreach ($array as $index => $value) {
$position = array_search ($index, array_keys ($array));
echo ($position);
}
... but I guess count++ is a much more resource-friendly way.
You can:
$count = count($array);
foreach($array as $key => $value) {
$count--;
//$count elements are left
}
Yes, sort of.
foreach($array as $key=>$value)
// code
}
$key will be the array key, although if you want an actual integer count of iterations, and the keys are not numbered sequentially, or are strings, you will have to use a counter like in your original post.
Edit: to handle the last element without implementing a counter, you can use (if keys are int)
$foo = ($key == count($array)-1) ? "last element" : "any other element";
(janked from the manual comments - http://php.net/manual/en/control-structures.foreach.php)
Edit: if your keys are not integers, you can create a counter like you have in your code above, and substitute $key with your counter variable.
You're being a bit too picky :)
By the way the trick is to transform an associative array to an indexed array:
foreach ( array_values($array) as $key=>$value ){
echo $key; //yes, it will be an INT
echo ( count($array) - $key );
}
Without some pre-processing, such as your count() version, there isn't any way to know where you are in an associative array. At most you can check if you're at the end with end(), but there's no guarantee as to what order foreach() will retrieve the individual elements. Generally it's the same order they got added to the array, but not guarantees.
Another pre-processing option would be
$keys = array_keys($array);
$cnt = count($keys)
for ($i = 0; $i < $cnt; $i++) {
$element = $array[$keys[$i]];
}
and $i would tell you exactly how far through you've gone.

Categories