PHP - Largest Triple Product solution - php

You're given a list of n integers arr[0..(n-1)]. You must compute a list output[0..(n-1)] such that, for each index i (between 0 and n-1, inclusive), output[i] is equal to the product of the three largest elements out of arr[0..i] (or equal to -1 if i < 2, as arr[0..i] then includes fewer than three elements). Note that the three largest elements used to form any product may have the same values as one another, but they must be at different indices in arr.
Test example:
var arr_2 = [2, 4, 7, 1, 5, 3];
var expected_2 = [-1, -1, 56, 56, 140, 140];
I solved the LPT problem using heaps in PHP. But I'm wondering if there's any way to further optimize this?
I'm creating a min heap of 3 and then pulling the top if it is less than the new element.
function findMaxProductWithHeap($arr): array
{
$out = [];
$product = 1;
$largest = new SplMinHeap();
foreach($arr as $index => $elem)
{
if($index <= 2){
$largest->insert($elem);
$product = $elem*$product;
$out[] = $index < 2 ? -1 : $product;
} else {
if($nums->top() < $elem){
$product = $elem*$product/$nums->top();
$largest->extract();
$largest->insert($elem);
}
$out[] = $product;
}
}
return $out;
}
I also solved this without heaps, just an array of 3 which is sorted on every iteration.
function findMaxProduct($arr) {
$out = [];
$product = 1;
$largest = [];
foreach($arr as $index => $elem)
{
sort($largest);
if($index <= 2){
$largest[] = $elem;
$product = $elem*$product;
$out[] = $index < 2 ? -1 : $product;
} else {
if($largest[0] < $elem){
$product = $elem*$product/$largest[0];
$largest[0] = $elem;
//var_dump($product);
}
$out[] = $product;
}
}
return $out;
}
Thanks.

No conditions, natsort, flexible optional params, works with primitive structures.
function solution($array, $heap_size = 3, $default = -1)
{
$result = [];
while ($array)
{
$heap[] = array_shift($array);
rsort($heap, SORT_NATURAL);
$heap = array_slice($heap, 0, $heap_size);
$result[] = array_product($heap);
}
$replacement = array_fill(0, min(count($result), $heap_size - 1), $default);
return $replacement + $result;
}

Related

All combinations of r elements from given array php

Given an array such as the following
$array = ('1', '2', '3', '4', '5', '6', '7');
I'm looking for a method to generate all possible combinations, with a minimum number of elements required in each combination r. (eg if r = 5 then it will return all possible combinations containing at least 5 elements)
Combinations of k out of n items can be defined recursively using the following function:
function combinationsOf($k, $xs){
if ($k === 0)
return array(array());
if (count($xs) === 0)
return array();
$x = $xs[0];
$xs1 = array_slice($xs,1,count($xs)-1);
$res1 = combinationsOf($k-1,$xs1);
for ($i = 0; $i < count($res1); $i++) {
array_splice($res1[$i], 0, 0, $x);
}
$res2 = combinationsOf($k,$xs1);
return array_merge($res1, $res2);
}
The above is based on the recursive definition that to choose k out n elements, one can fix an element x in the list, and there are C(k-1, xs\{x}) combinations that contain x (i.e. res1), and C(k,xs\{xs}) combinations that do not contain x (i.e. res2 in code).
Full example:
$array = array('1', '2', '3', '4', '5', '6', '7');
function combinationsOf($k, $xs){
if ($k === 0)
return array(array());
if (count($xs) === 0)
return array();
$x = $xs[0];
$xs1 = array_slice($xs,1,count($xs)-1);
$res1 = combinationsOf($k-1,$xs1);
for ($i = 0; $i < count($res1); $i++) {
array_splice($res1[$i], 0, 0, $x);
}
$res2 = combinationsOf($k,$xs1);
return array_merge($res1, $res2);
}
print_r ($array);
print_r(combinationsOf(5,$array));
//print_r(combinationsOf(5,$array)+combinationsOf(6,$array)+combinationsOf(7,$array));
A combination can be expressed as
nCr = n! / (r! - (n - r)!)
First, we determine $n as the number of elements in the array. And $r is the minimum number of elements in each combination.
$a = ['1', '2', '3', '4', '5', '6', '7']; // the array of elements we are interested in
// Determine the `n` and `r` in nCr = n! / (r! * (n-r)!)
$r = 5;
$n = count($a);
Next, we determine $max as the maximum number that can be represented by $n binary digits. That is, if $n = 3, then $max = (111)2 = 7. To do this, we first create a empty string $maxBinary and add $n number of 1s to it. We then convert it to decimal, and store it in $max.
$maxBinary = "";
for ($i = 0; $i < $n; $i++)
{
$maxBinary .= "1";
}
$max = bindec($maxBinary); // convert it into a decimal value, so that we can use it in the following for loop
Then, we list out every binary number from 0 to $max and store those that have more than $r number of 1s in them.
$allBinary = array(); // the array of binary numbers
for ($i = 0; $i <= $max; $i++)
{
if (substr_count(decbin($i), "1") >= $r) // we count the number of ones to determine if they are >= $r
{
// we make the length of the binary numbers equal to the number of elements in the array,
// so that it is easy to select elements from the array, based on which of the digits are 1.
// we do this by padding zeros to the left.
$temp = str_pad(decbin($i), $n, "0", STR_PAD_LEFT);
$allBinary[] = $temp;
}
}
Then, we use the same trick as above to select elements for our combination. I believe the comments explain enough.
$combs = array(); // the array for all the combinations.
$row = array(); // the array of binary digits in one element of the $allBinary array.
foreach ($allBinary as $key => $one)
{
$combs[$key] = "";
$row = str_split($one); // we store the digits of the binary number individually
foreach ($row as $indx => $digit)
{
if ($digit == '1') // if the digit is 1, then the corresponding element in the array is part of this combination.
{
$combs[$key] .= $a[$indx]; // add the array element at the corresponding index to the combination
}
}
}
And that is it. You are done!
Now if you have something like
echo count($combs);
then it would give you 29.
Additional notes:
I read up on this only after seeing your question, and as a newcomer, I found these useful:
Wikipedia - http://en.wikipedia.org/wiki/Combination
Php recursion to get all possibilities of strings
Algorithm to return all combinations of k elements from n
Also, here are some quick links to the docs, that should help people who see this in the future:
http://php.net/manual/en/function.decbin.php
http://php.net/manual/en/function.bindec.php
http://php.net/manual/en/function.str-pad.php
function arrToBit(Array $element) {
$bit = '';
foreach ($element as $e) {
$bit .= '1';
}
$length = count($element);
$num = bindec($bit);
$back = [];
while ($num) {
$back[] = str_pad(decbin($num), $length, '0', STR_PAD_LEFT);
$num--;
}
//$back[] = str_pad(decbin(0), $length, '0', STR_PAD_LEFT);
return $back;
}
function bitToArr(Array $element, $bit) {
$num = count($element);
$back = [];
for ($i = 0; $i < $num; $i++) {
if (substr($bit, $i, 1) == '1') {
$back[] = $element[$i];
}
}
return $back;
}
$tags = ['a', 'b', 'c'];
$bits = arrToBit($tags);
$combination = [];
foreach ($bits as $b) {
$combination[] = bitToArr($tags, $b);
}
var_dump($combination);
$arr = array(1,2,3,4,5,6);
$check_value =[];
$all_values = [];
CONT:
$result = $check_value;
shuffle($arr);
$check_value = array_slice($arr,0,3);
if(count($check_value) == 3 && serialize($check_value) !== serialize($result)){
$result = $check_value;
array_push($all_values,$result);
goto CONT;
}
print_r($all_values);

Counting the number of dimensions in an array [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is there a way to find how how “deep” a PHP array is?
I am trying to write a method to count the number of dimensions of an array. The following gives me a correct count of dimensions
$array = array();
$array[0] = array();
$array[0][0] = 0;
$array[0][1] = array();
$array[0][1][0] = 10;
$array[0][1][1] = 11;
echo '<p>'.\utility\arrayTools\arrayTools::numberOfDimensions($array).'</p>';
//3 Dimensons
The second examples also gives me a correct count of the number of dimensions
$array = array();
$array[0] = array();
$array[0][0] = 0;
$array[0][1] = array();
$array[0][1][0] = 10;
$array[0][1][1] = 11;
$array[1] = 1;
$array[2] = 2;
//3 Dimensions
But the following example gives me too high of a count
$array = array();
$array[0] = array();
$array[0][0] = 0;
$array[0][1] = array();
$array[0][1][0] = 10;
$array[0][1][1] = 11;
$array[1] = 1;
$array[2] = 2;
$array[3] = array();
$array[3][0] = 30;
//Should still be 3 dimensions, but gives me 4
The method I am using is below
//Method
public static function numberOfDimensions($array)
{
if(func_num_args() === 2){
if(is_int(func_get_arg(1))){
$number_of_dimensions = func_get_arg(1);
}else{
throw new Exception('The second argumment must be an interger');
}
}else{
$number_of_dimensions = 0;
}
if(is_array($array) === TRUE){
$number_of_dimensions++;
if(self::isMultiDimensional($array) === TRUE){
foreach($array as $iteration){
$number_of_dimensions = self::numberOfDimensions($iteration,$number_of_dimensions);
}
return $number_of_dimensions;
}else{
return $number_of_dimensions;
}
}else{
return $number_of_dimensions;
}
}
I already know the problem is it is still adding for every multidimensional even though the count may be equal to or less then the number of dimensions. But what I can't figure out is how to get it to find the highest number of dimensions and stop counting
Here is a simpler version of your script
function numberOfDimensions($array) {
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
$d = 0;
foreach ( $it as $v )
$it->getDepth() >= $d and $d = $it->getDepth();
return ++ $d;
}
From another answer here on SO:
function array_depth($array) {
$max_depth = 1;
foreach ($array as $value) {
if (is_array($value)) {
$depth = array_depth($value) + 1;
if ($depth > $max_depth) {
$max_depth = $depth;
}
}
}
return $max_depth;
}
I havn't written any PHP in a very long while, so there may very well be mistakes in my code, excuse me in advance.
function numberOfDimensions($array,$so_far=0){
if !is_array($array){
return 0;
}
$max_dims = 0;
foreach($array as $element){
$element_dims = numberOfDimensions($array);
$max_dims = max($max_dims,$element_dims);
}
return $max_dims +1;
}
This function assumes it is being called with an array as parameter, thus the minimum depths is 1.
It then recursively looks at all child elements, and if the child is deeper than already known, it add the depth of the child to the calculated depth:
function numberOfDimensions($subject) {
// scalar value has depth 0
if(!is_array($subject)) return 0;
// array has min depth of 1
$depth = 1;
foreach ($subject as $element) {
if (is_array($element)) {
// is the sub array deeper than already known?
$sub_depth = numberOfDimensions($element);
if ($sub_depth >= $depth) {
$depth += $sub_depth;
}
}
}
return $depth;
}

Getting the most repeated values in an array [duplicate]

This question already has answers here:
PHP get the item in an array that has the most duplicates
(2 answers)
Closed 1 year ago.
I have an array of numbers like this:
$array = array(1,1,1,4,3,1);
How do I get the count of most repeated value?
This should work:
$count=array_count_values($array);//Counts the values in the array, returns associatve array
arsort($count);//Sort it from highest to lowest
$keys=array_keys($count);//Split the array so we can find the most occuring key
echo "The most occuring value is $keys[0][1] with $keys[0][0] occurences."
I think array_count_values function can be useful to you. Look at this manual for details : http://php.net/manual/en/function.array-count-values.php
You can count the number of occurrences of values in an array with array_count_values:
$counts = array_count_values($array);
Then just do a reverse sort on the counts:
arsort($counts);
Then check the top value to get your mode.
$mode = key($counts);
If your array contains strings or integers only you can use array_count_values and arsort:
$array = array(1, 1, 1, 4, 3, 1);
$counts = array_count_values($array);
arsort($counts);
That would leave the most used element as the first one of $counts. You can get the count amount and value afterwards.
It is important to note that if there are several elements with the same amount of occurrences in the original array I can't say for sure which one you will get. Everything depends on the implementations of array_count_values and arsort. You will need to thoroughly test this to prevent bugs afterwards if you need any particular one, don't make any assumptions.
If you need any particular one, you'd may be better off not using arsort and write the reduction loop yourself.
$array = array(1, 1, 1, 4, 3, 1);
/* Our return values, with some useless defaults */
$max = 0;
$max_item = $array[0];
$counts = array_count_values($array);
foreach ($counts as $value => $amount) {
if ($amount > $max) {
$max = $amount;
$max_item = $value;
}
}
After the foreach loop, $max_item contains the last item that appears the most in the original array as long as array_count_values returns the elements in the order they are found (which appears to be the case based on the example of the documentation). You can get the first item to appear the most in your original array by using a non-strict comparison ($amount >= $max instead of $amount > $max).
You could even get all elements tied for the maximum amount of occurrences this way:
$array = array(1, 1, 1, 4, 3, 1);
/* Our return values */
$max = 0;
$max_items = array();
$counts = array_count_values($array);
foreach ($counts as $value => $amount) {
if ($amount > $max) {
$max = $amount;
$max_items = array($value);
} elif ($amount = $max) {
$max_items[] = $value;
}
}
$vals = array_count_values($arr);
asort($vals);
//you may need this end($vals);
echo key($vals);
I cant remember if asort sorts asc or desc by default, you can see the comment in the code.
<?php
$arrrand = '$arr = array(';
for ($i = 0; $i < 100000; $i++)
{
$arrrand .= rand(0, 1000) . ',';
}
$arrrand = substr($arrrand, 0, -1);
$arrrand .= ');';
eval($arrrand);
$start1 = microtime();
$count = array_count_values($arr);
$end1 = microtime();
echo $end1 - $start1;
echo '<br>';
$start2 = microtime();
$tmparr = array();
foreach ($arr as $key => $value);
{
if (isset($tmparr[$value]))
{
$tmparr[$value]++;
} else
{
$tmparr[$value] = 1;
}
}
$end2 = microtime();
echo $end2 - $start2;
Here check both solutions:
1 by array_count_values()
and one by hand.
<?php
$input = array(1,2,2,2,8,9);
$output = array();
$maxElement = 0;
for($i=0;$i<count($input);$i++) {
$count = 0;
for ($j = 0; $j < count($input); $j++) {
if ($input[$i] == $input[$j]) {
$count++;
}
}
if($count>$maxElement){
$maxElement = $count;
$a = $input[$i];
}
}
echo $a.' -> '.$maxElement;
The output will be 2 -> 3
$arrays = array(1, 2, 2, 2, 3, 1); // sample array
$count=array_count_values($arrays); // getting repeated value with count
asort($count); // sorting array
$key=key($count);
echo $arrays[$key]; // get most repeated value from array
String S;
Scanner in = new Scanner(System.in);
System.out.println("Enter the String: ");
S = in.nextLine();
int count =1;
int max = 1;
char maxChar=S.charAt(0);
for(int i=1; i <S.length(); i++)
{
count = S.charAt(i) == S.charAt(i - 1) ? (count + 1):1;
if(count > max)
{
max = count;
maxChar = S.charAt(i);
}
}
System.out.println("Longest run: "+max+", for the character "+maxChar);
here is the solution
class TestClass {
public $keyVal;
public $keyPlace = 0;
//put your code here
public function maxused_num($array) {
$temp = array();
$tempval = array();
$r = 0;
for ($i = 0; $i <= count($array) - 1; $i++) {
$r = 0;
for ($j = 0; $j <= count($array) - 1; $j++) {
if ($array[$i] == $array[$j]) {
$r = $r + 1;
}
}
$tempval[$i] = $r;
$temp[$i] = $array[$i];
}
//fetch max value
$max = 0;
for ($i = 0; $i <= count($tempval) - 1; $i++) {
if ($tempval[$i] > $max) {
$max = $tempval[$i];
}
}
//get value
for ($i = 0; $i <= count($tempval) - 1; $i++) {
if ($tempval[$i] == $max) {
$this->keyVal = $tempval[$i];
$this->keyPlace = $i;
break;
}
}
// 1.place holder on array $this->keyPlace;
// 2.number of reapeats $this->keyVal;
return $array[$this->keyPlace];
}
}
$catch = new TestClass();
$array = array(1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 1, 2, 3, 1, 1, 2, 5, 7, 1, 9, 0, 11, 22, 1, 1, 22, 22, 35, 66, 1, 1, 1);
echo $catch->maxused_num($array);

How to count array values and keep only the ones that are relevant?

I have an array that contains multiple integers, I'm interested only in integers that repeat themselves a certain number of times. For example:
$items = (0, 0, 0, 1, 1, 2, 3, 3, 3)
I want to know which item(s) are repeated exactly $number (in this example $number = 3) times (in this example new array $items = (0, 3)).
If none of the array items is repeated $number times, I need to have var $none = 1.
I know for a function array_count_values but don't know how to implement it to my case...
$number = 3;
$items = array_keys(array_filter(array_count_values($items), create_function('$n', "return \$n == $number;")));
if (!$items) {
$none = 1;
}
use array_count_values to get a pairing of how often each number occurs
filter this through an array_filter callback that discards all entries except those that have a count of $number
take the keys of the resulting array (the actual counted values)
the resulting array is either empty or contains the values that occur $number of times
I know there are a lot of solutions, but thought I'd add one more. ;-)
function array_repeats($items,$repeats,&$none){
$result = array();
foreach (array_unique($items) as $item){
$matches = array_filter($items,create_function('$a','return ($a=='.$item.');'));
if (count($matches) == $repeats)
$result[] = $item;
}
$none = (count($result)?1:0);
return $result;
}
DEMO
$repeated_items will be an array containing only your desired items.
$limit = 3; //your limit for repetition
$catch = array();
foreach ($items as $item){
if(array_key_exists($item, $catch)){
$catch[$item]++;
} else {
$catch[$item] = 1;
}
}
$repeated_items = array();
foreach ($catch as $k=>$caught){
if($caught>=$limit){
$repeated_items[]=$k;
}
}
Some pseudo-code to get you started:
Sort your array in order to get similar items together
Foreach item
if current item == previous item then
repeat count ++
else
if repeat count > limit then
add current item to new array
$items = array(0, 0, 0, 1, 1, 2, 3, 3, 3);
$count = array_count_values($items);
$number = 3;
$none = 1;
$result = array();
foreach(array_unique($items) as $item) {
if($count[$item] == $number) {
$result[] = $item;
$none = 0;
}
}
$items = array(0, 0, 0, 1, 1, 2, 3, 3, 3);
$none=1;
$new_array=array();
$n=3;
dojob($items,$n,$none,$new_array);
function dojob($items,$n,&$none,&$new_array)
{
$values_count=array_count_values($items);
foreach($values_count as $value => $count)
{
if($count ==$n)
{
$none=0;
$new_array[]=$value;
}
}
}
A bit late, but:
<?php
$items = array(0, 0, 0, 1, 1, 2, 3, 3, 3);
$temp = array_unique($items);
$result = array();
$none = 1;
$number = 3;
foreach($temp as $tmp)
{
if(count(array_keys($items, $tmp)) == $number)
{
array_push($result,$tmp);
$none = 0;
}
}
print_r($result);
?>
$items = array(0, 0, 0, 1, 1, 2, 3, 3, 3);
$icnt = array_count_values($items);
function eq3($v) {
return $v==3;
}
var_export(array_filter($icnt, 'eq3'));
will produce array ( 0 => 3, 3 => 3, ). In your example 0 and 3 repeat 3 times. Array_filter is needed here to, actually, filter your resulting array and get rid of necessary values, but you was right about using array_count_values here.
One way would be to create a kind of hash table and loop over every item in your array.
$items = array(0, 0, 0, 1, 1, 2, 3, 3, 3);
$number = 3;
$none = 1;
foreach ($items as $value) {
if ($hash[$value] >= $number) {
# This $value has occured as least $number times. Lets save it.
$filtered_items[] = $value;
# We have at least one item in the $items array >= $number times
# so set $none to 0
$none = 0;
# No need to keep adding
continue;
} else {
# Increment the count of each value
$hash[$value]++;
}
}
$items = $filtered_items;

Find a matching or closest value in an array

How can I search and find, for a given target value, the closest value in an array?
Let's say I have this exemplary array:
array(0, 5, 10, 11, 12, 20)
For example, when I search with the target value 0, the function shall return 0; when I search with 3, it shall return 5; when I search with 14, it shall return 12.
Pass in the number you're searching for as the first parameter and the array of numbers to the second:
function getClosest($search, $arr) {
$closest = null;
foreach ($arr as $item) {
if ($closest === null || abs($search - $closest) > abs($item - $search)) {
$closest = $item;
}
}
return $closest;
}
A particular lazy approach is having PHP sort the array by the distance to the searched number:
$num = 3;
$array = array(0, 5, 10, 11, 12, 20);
$smallest = [];
foreach ($array as $i) {
$smallest[$i] = abs($i - $num);
}
asort($smallest);
print key($smallest);
This is high-performance function I wrote for sorted big arrays
Tested, main loop needs only ~20 iterations for an array with 20000 elements.
Please mind array has to be sorted (ascending)!
define('ARRAY_NEAREST_DEFAULT', 0);
define('ARRAY_NEAREST_LOWER', 1);
define('ARRAY_NEAREST_HIGHER', 2);
/**
* Finds nearest value in numeric array. Can be used in loops.
* Array needs to be non-assocative and sorted.
*
* #param array $array
* #param int $value
* #param int $method ARRAY_NEAREST_DEFAULT|ARRAY_NEAREST_LOWER|ARRAY_NEAREST_HIGHER
* #return int
*/
function array_numeric_sorted_nearest($array, $value, $method = ARRAY_NEAREST_DEFAULT) {
$count = count($array);
if($count == 0) {
return null;
}
$div_step = 2;
$index = ceil($count / $div_step);
$best_index = null;
$best_score = null;
$direction = null;
$indexes_checked = Array();
while(true) {
if(isset($indexes_checked[$index])) {
break ;
}
$curr_key = $array[$index];
if($curr_key === null) {
break ;
}
$indexes_checked[$index] = true;
// perfect match, nothing else to do
if($curr_key == $value) {
return $curr_key;
}
$prev_key = $array[$index - 1];
$next_key = $array[$index + 1];
switch($method) {
default:
case ARRAY_NEAREST_DEFAULT:
$curr_score = abs($curr_key - $value);
$prev_score = $prev_key !== null ? abs($prev_key - $value) : null;
$next_score = $next_key !== null ? abs($next_key - $value) : null;
if($prev_score === null) {
$direction = 1;
}else if ($next_score === null) {
break 2;
}else{
$direction = $next_score < $prev_score ? 1 : -1;
}
break;
case ARRAY_NEAREST_LOWER:
$curr_score = $curr_key - $value;
if($curr_score > 0) {
$curr_score = null;
}else{
$curr_score = abs($curr_score);
}
if($curr_score === null) {
$direction = -1;
}else{
$direction = 1;
}
break;
case ARRAY_NEAREST_HIGHER:
$curr_score = $curr_key - $value;
if($curr_score < 0) {
$curr_score = null;
}
if($curr_score === null) {
$direction = 1;
}else{
$direction = -1;
}
break;
}
if(($curr_score !== null) && ($curr_score < $best_score) || ($best_score === null)) {
$best_index = $index;
$best_score = $curr_score;
}
$div_step *= 2;
$index += $direction * ceil($count / $div_step);
}
return $array[$best_index];
}
ARRAY_NEAREST_DEFAULT finds nearest element
ARRAY_NEAREST_LOWER finds nearest element which is LOWER
ARRAY_NEAREST_HIGHER finds nearest element which is HIGHER
Usage:
$test = Array(5,2,8,3,9,12,20,...,52100,52460,62000);
// sort an array and use array_numeric_sorted_nearest
// for multiple searches.
// for every iteration it start from half of chunk where
// first chunk is whole array
// function doesn't work with unosrted arrays, and it's much
// faster than other solutions here for sorted arrays
sort($test);
$nearest = array_numeric_sorted_nearest($test, 8256);
$nearest = array_numeric_sorted_nearest($test, 3433);
$nearest = array_numeric_sorted_nearest($test, 1100);
$nearest = array_numeric_sorted_nearest($test, 700);
<?php
$arr = array(0, 5, 10, 11, 12, 20);
function getNearest($arr,$var){
usort($arr, function($a,$b) use ($var){
return abs($a - $var) - abs($b - $var);
});
return array_shift($arr);
}
?>
Tim's implementation will cut it most of the time. Nevertheless, for the performance cautious, you can sort the list prior to the iteration and break the search when the next difference is greater than the last.
<?php
function getIndexOfClosestValue ($needle, $haystack) {
if (count($haystack) === 1) {
return $haystack[0];
}
sort($haystack);
$closest_value_index = 0;
$last_closest_value_index = null;
foreach ($haystack as $i => $item) {
if (abs($needle - $haystack[$closest_value_index]) > abs($item - $needle)) {
$closest_value_index = $i;
}
if ($closest_value_index === $last_closest_value_index) {
break;
}
}
return $closest_value_index;
}
function getClosestValue ($needle, $haystack) {
return $haystack[getIndexOfClosestValue($needle, $haystack)];
}
// Test
$needles = [0, 2, 3, 4, 5, 11, 19, 20];
$haystack = [0, 5, 10, 11, 12, 20];
$expectation = [0, 0, 1, 1, 1, 3, 5, 5];
foreach ($needles as $i => $needle) {
var_dump( getIndexOfClosestValue($needle, $haystack) === $expectation[$i] );
}
To search the nearest value into an array of objects you can use this adapted code from Tim Cooper's answer.
<?php
// create array of ten objects with random values
$images = array();
for ($i = 0; $i < 10; $i++)
$images[ $i ] = (object)array(
'width' => rand(100, 1000)
);
// print array
print_r($images);
// adapted function from Tim Copper's solution
// https://stackoverflow.com/a/5464961/496176
function closest($array, $member, $number) {
$arr = array();
foreach ($array as $key => $value)
$arr[$key] = $value->$member;
$closest = null;
foreach ($arr as $item)
if ($closest === null || abs($number - $closest) > abs($item - $number))
$closest = $item;
$key = array_search($closest, $arr);
return $array[$key];
}
// object needed
$needed_object = closest($images, 'width', 320);
// print result
print_r($needed_object);
?>
Best method I've found based on Piyush Dholariya's answer:
$array = [4, 9, 15, 6, 2];
$goal = 7;
$closest = array_reduce($array, function($carry, $item) use($goal) {
return (abs($item - $goal) < abs($carry - $goal) ? $item : $carry);
}, reset($array)); // Returns 6
This is the same approach as Mario's answer, but I use array_search() and min() instead of sorting. The performance is the same, so it just comes down to the matter of preference.
function findClosest(array $values, $match)
{
$map = [];
foreach ($values as $v) {
$map[$v] = abs($match - $v);
}
return array_search(min($map), $map);
}
You can simply use array_search for that, it returns one single key, if there are many instances of your search found within the array, it would return the first one it finds.
Quote from PHP:
If needle is found in haystack more than once, the first matching key is returned. To return the keys for all matching values, use array_keys() with the optional search_value parameter instead.
Example Usage:
if(false !== ($index = array_search(12,array(0, 5, 10, 11, 12, 20))))
{
echo $index; //5
}
Update:
function findNearest($number,$Array)
{
//First check if we have an exact number
if(false !== ($exact = array_search($number,$Array)))
{
return $Array[$exact];
}
//Sort the array
sort($Array);
//make sure our search is greater then the smallest value
if ($number < $Array[0] )
{
return $Array[0];
}
$closest = $Array[0]; //Set the closest to the lowest number to start
foreach($Array as $value)
{
if(abs($number - $closest) > abs($value - $number))
{
$closest = $value;
}
}
return $closest;
}
Considering that the input array is sorted in ascending order asort() for example, you'll be far faster to search using a dichotomic search.
Here's a quick and dirty adaptation of some code I'm using to insert a new event in an Iterable event list sorted by DateTime objects…
Thus this code will return the nearest point at the left (before / smaller).
If you'd like to find the mathematically nearest point: consider comparing the distance of the search value with the return value and the point immediately at the right (next) of the return value (if it exists).
function dichotomicSearch($search, $haystack, $position=false)
{
// Set a cursor between two values
if($position === false)
{ $position=(object) array(
'min' => 0,
'cur' => round(count($haystack)/2, 0, PHP_ROUND_HALF_ODD),
'max' => count($haystack)
);
}
// Return insertion point (to push using array_splice something at the right spot in a sorted array)
if(is_numeric($position)){return $position;}
// Return the index of the value when found
if($search == $haystack[$position->cur]){return $position->cur;}
// Searched value is smaller (go left)
if($search <= $haystack[$position->cur])
{
// Not found (closest value would be $position->min || $position->min+1)
if($position->cur == $position->min){return $position->min;}
// Resetting the interval from [min,max[ to [min,cur[
$position->max=$position->cur;
// Resetting cursor to the new middle of the interval
$position->cur=round($position->cur/2, 0, PHP_ROUND_HALF_DOWN);
return dichotomicSearch($search, $haystack, $position);
}
// Search value is greater (go right)
// Not found (closest value would be $position->max-1 || $position->max)
if($position->cur < $position->min or $position->cur >= $position->max){return $position->max;}
// Resetting the interval from [min,max[ to [cur,max[
$position->min = $position->cur;
// Resetting cursor to the new middle of the interval
$position->cur = $position->min + round(($position->max-$position->min)/2, 0, PHP_ROUND_HALF_UP);
if($position->cur >= $position->max){return $position->max;}
return dichotomicSearch($search, $haystack, $position);
}
Binary search to find closest value (array must be sorted):
function findClosest($sortedArr, $val)
{
$low = 0;
$high = count($sortedArr) - 1;
while ($low <= $high) {
if ($high - $low <= 1) {
if (abs($sortedArr[$low] - $val) < abs($sortedArr[$high] - $val)) {
return $sortedArr[$low];
} else {
return $sortedArr[$high];
}
}
$mid = (int)(($high + $low) / 2);
if ($val < $sortedArr[$mid]) {
$high = $mid;
} else {
$low = $mid;
}
}
// Empty array
return false;
}
function closestnumber($number, $candidates) {
$last = null;
foreach ($candidates as $cand) {
if ($cand < $number) {
$last = $cand;
} elseif ($cand == $number) {
return $number;
} elseif ($cand > $number) {
return $last;
}
}
return $last;
}
I'll provide a late answer that endeavors to avoid needless iterations and excessive function calls by maintaining two temporary variables and implementing an early return.
An elegant solution should not require a time complexity greater than n -- in other words, the big O should be O(n) and the little o should be o(1). The big O only gets worse by pre-sorting the haystack, then iterating the haystack again. To get achieve o(1), you will need an early return when an identical match is encountered -- there is no need to search further.
My snippet will arbitrarily return the first occurring value with the lowest distance (in case multiple values have the same distance). Any other behavior is not specified by the OP.
A trivial performance improvement over some other answers is that abs() is the lone function call within the loop and it is called a maximum of 1 time per iteration. Some previous answers recalculate the distance of the current value as well as the current closest match on each iteration -- this is more work than is necessary.
Code: (Demo)
$haystack = [-6, 0, 5, 10, 11, 12, 20];
$needles = [0, 3, 14, -3];
function getNearest($needle, $haystack) {
if (!$haystack) {
throw new Exception('empty haystack');
}
$bestDistance = PHP_INT_MAX;
foreach ($haystack as $value) {
if ($value === $needle) {
return $needle;
}
$distance = abs($value - $needle);
if ($distance < $bestDistance) {
$bestDistance = $distance;
$keep = $value;
}
}
return $keep ?? $value; // coalesce to silence potential IDE complaint
}
foreach ($needles as $needle) { // each test case
echo "$needle -> " . getNearest($needle, $haystack) . "\n";
}
Output:
0 -> 0
3 -> 5
14 -> 12
-3 -> -6

Categories