PHP - Retrieve min() object from multidimentional object array - php

** EDIT ** What would happen if I only used arrays, e.g.
array(
array('name' => 'bla', 'distance' => '123');
array('name' => 'b123a', 'distance' => '1234214');
);
Would this be easier to find the min value ?
Hi there I'm trying to retrieve the object which has the lowest distance value from an array of objects. This is my data set below;
[0] => myObjectThing Object
(
[name:myObjectThing:private] => asadasd
[distance:myObjectThinge:private] => 0.9826368952306
)
[1] => myObjectThing Object
(
[name:myObjectThing:private] => 214gerwert24
[distance:myObjectThinge:private] => 1.5212312547306
)
[2] => myObjectThing Object
(
[name:myObjectThing:private] => abc123
[distance:myObjectThinge:private] => 0.0000368952306
)
So I'd like to be able to retieve the object which has the smallest distance value. In this case it would be object with name: abc123

Hmm for PHP >= 5.3 I would try something like this:
$Object = array_reduce($data,function($A,$B){
return $A->distance < $B->distance ? $A : $B;
})
For PHP < 5.3 the fillowing would suffice:
function user_defined_reduce($A,$B){
return $A->distance < $B->distance ? $A : $B;
}
$Object = array_reduce($data,"user_defined_reduce");

Previously suggested answers did not explain the need to include an $initial value to array_reduce(). The solution did not work because of it.
This one works for me (PHP 5.3.13):
$array = array(
array(
'name' => 'something',
'value' => 56
),
array(
'name' => 'else',
'value' => 54
),
array(
'name' => 'else',
'value' => 58
),
array(
'name' => 'else',
'value' => 78
)
);
$object = array_reduce($array, function($a, $b){
return $a['value'] < $b['value'] ? $a : $b;
}, array_shift($array));
print_r($object);
This will give me:
[0] => Array
(
[name] => else
[value] => 54
)
Whereas the previous solution gave me null. I'm assuming that PHP < 5.3 would require a similar initial value to be specified to array_reduce().

You can't flatten this as it isn't a plain old multi-dimensional array with just the values.
This should work:
$min = $object[0];
for ($i = 1; $i < count($object); $i++)
if ($object[$i]['distance'] < $min['distance'])
$min = $object[$i];

This example should give you a hint in the right direction:
<?php
$a = new stdClass();
$a->foo = 2;
$b = new stdClass();
$b->foo = 3;
$c = new stdClass();
$c->foo = 1;
$init = new stdClass();
$init->foo = 1000;
$vals = array( $a, $b, $c );
var_dump(
array_reduce(
$vals,
function ( $x, $y )
{
if ( $x->foo < $y->foo )
{
return $x;
}
else
{
return $y;
}
},
$init
)
);
?>

try:
$myObjectCollection = ...;
$minObject = $myObjectCollection[0]; // or reset($myObjectCollection) if you can't ensure numeric 0 index
array_walk($myObjectCollection, function($object) use ($minObject) {
$minObject = $object->distance < $minObject->distance ? $object : $minObject;
});
but from the looks of your dump. name and distance are private. So you want be able to access them from the object directly using ->. You'll need some kind of getter getDistance()

$objectsArray = array(...); // your objects
$distance = null;
$matchedObj = null;
foreach ( $objectsArray as $obj ) {
if ( is_null($distance) || ( $obj->distance < $distance ) ) {
$distance = $obj->distance;
$matchedObj = $obj;
}
}
var_dump($matchedObj);
AD EDIT:
If you will use arrays instead of objects, change $obj->distance to $obj['distance'].

Related

PHP - add values to already existing array

I have a already defined array, containing values just like the one below:
$arr = ['a','b','c'];
How could one add the following using PHP?
$arr = [
'a' => 10,
'b' => 5,
'c' => 21
]
I have tried:
$arr['a'] = 10 but it throws the error: Undefined index: a
I am surely that I do a stupid mistake.. could someone open my eyes?
Full code below:
$finishes = []; //define array to hold finish types
foreach ($projectstages as $stage) {
if ($stage->finish_type) {
if(!in_array($stage->finish_type, $finishes)){
array_push($finishes, $stage->finish_type);
}
}
}
foreach ($projectunits as $unit) {
$data[$i] = [
'id' => $unit->id,
'project_name' => $unit->project_name,
'block_title' => $unit->block_title,
'unit' => $unit->unit,
'core' => $unit->core,
'floor' => $unit->floor,
'unit_type' => $unit->unit_type,
'tenure_type' => $unit->tenure_type,
'floors' => $unit->unit_floors,
'weelchair' => $unit->weelchair,
'dual_aspect' => $unit->dual_aspect
];
$st = array();
$bs = '';
foreach ($projectstages as $stage) {
$projectmeasure = ProjectMeasure::select('measure')
->where('project_id',$this->projectId)
->where('build_stage_id', $stage->id)
->where('unit_id', $unit->id)
->where('block_id', $unit->block_id)
->where('build_stage_type_id', $stage->build_stage_type_id)
->first();
$st += [
'BST-'.$stage->build_stage_type_id => ($projectmeasure ? $projectmeasure->measure : '0')
];
if (($stage->is_square_meter == 0) && ($stage->is_draft == 0)) {
$height = ($stage->height_override == 0 ? $unit->gross_floor_height : $stage->height_override); //08.14.20: override default height if build stage type has it's own custom height
$st += [
'BST-sqm-'.$stage->build_stage_type_id => ($projectmeasure ? $projectmeasure->measure * $height: '0')
];
if ($stage->finish_type) {
$finishes[$stage->finish_type] += ($projectmeasure ? $projectmeasure->measure * $height: '0') * ($stage->both_side ? 2 : 1); //error is thrown at this line
}
} else {
if ($stage->finish_type) {
$finishes[$stage->finish_type] += ($projectmeasure ? $projectmeasure->measure : '0');
}
}
}
$data[$i] = array_merge($data[$i], $st);
$data[$i] = array_merge($data[$i], $finishes[$stage->finish_type]);
$i++;
}
The above code is used as is and the array $finishes is the one from the first example, called $arr
You're using += in your real code instead of =. That tries to do maths to add to an existing value, whereas = can just assign a new index with that value if it doesn't exist.
+= can't do maths to add a number to nothing. You need to check first if the index exists yet. If it doesn't exist, then assign it with an initial value. If it already exists with a value, then you can add the new value to the existing value.
If you want to convert the array of strings to a collection of keys (elements) and values (integers), you can try the following:
$arr = ['a','b','c'];
$newVals = [10, 5, 21];
function convertArr($arr, $newVals){
if(count($arr) == count($newVals)){
$len = count($arr);
for($i = 0; $i < $len; $i++){
$temp = $arr[$i];
$arr[$temp] = $newVals[$i];
unset($arr[$i]);
}
}
return $arr;
}
print_r(convertArr($arr, $newVals));
Output:
Array ( [a] => 10 [b] => 5 [c] => 21 )

Sorting Array by an added variable

Adding a variable to the end of a mysql query.
Now I need to be able to sort by the associated value. Can someone help? I have tried several different sorting methods.
$result2 = mysqli_query($datacon,
"SELECT * FROM spreadsheet
WHERE brand = '$brand'
AND package = '$package'
AND HIDE_FROM_SEARCH = '0'
GROUP BY ACCTNAME $pages->limit");
echo mysqli_error($datacon);
while($row = mysqli_fetch_array($result2)) {
$lat_B=$row['LATITUDE'];
$long_B=$row['LONGITUDE'];
$lat_A = round($lat_A,2);
$long_A = round($long_A,2);
$lat_B = round($lat_B,2);
$long_B = round($long_B,2);
$distance = new calcDist($lat_A, $long_A, $lat_B, $long_B);
$distance = $distance->calcDist($lat_A, $long_A, $lat_B, $long_B);
$distance = round($distance,2);
$locationSort=array($row);
$locationSort['distance']=$distance;
FOR EACH SOMETHING?
}
I have amended my previous post to reflect my usage of an answer.
$result2 = mysqli_query($datacon,"SELECT * FROM spreadsheet WHERE brand = '$brand'
AND package = '$package' and HIDE_FROM_SEARCH = '0' GROUP BY ACCTNAME $pages->limit");
echo(mysqli_error($datacon));
class sortBydistance
{
function sortBydistance($a, $b) {
if ($a['distance'] == $b['distance']) {
return 0;
}
return ($a['distance'] < $b['distance']) ? -1 : 1;
}
}
while($row = mysqli_fetch_array($result2))
{
$lat_B=$row['LATITUDE'];
$long_B=$row['LONGITUDE'];
$lat_A = round($lat_A,2);
$long_A = round($long_A,2);
$lat_B = round($lat_B,2);
$long_B = round($long_B,2);
$distance = new calcDist($lat_A, $long_A, $lat_B, $long_B);
$distance = $distance->calcDist($lat_A, $long_A, $lat_B, $long_B);
$distance = round($distance,2);
}
$locationSort=array($row);
$locationSort['distance']=$distance;
uasort($locationSort, array($this, "sortBydistance"));
print_r($locationSort);
Probably you are not retrieving correctly the $bran and $package variables..
$result2 = mysqli_query($datacon,
"SELECT * FROM spreadsheet
WHERE brand = '".$brand."'
AND package = '".$package."'
AND HIDE_FROM_SEARCH = '0'
GROUP BY ACCTNAME ".$pages->limit.");
First, create your array of entries, and store it in an array, we will call $rows.
You want to use the PHP function usort here to provide a custom sorting callback.
Let's say you have an array like the following:
$rows = array(
array("latitude" => 5, "distance" => 10),
array("latitude" => 10, "distance" => 5),
array("latitude" => 56, "distance" => 48)
);
You expect this array of $rows to be reordered so that the entry with latitude == 10 is first, latitude == 5 is second, and latitude == 56 is third, based on their distance.
usort($rows, function ($a, $b) {
$a = $a['distance'];
$b = $b['distance'];
return $a == $b ? 0 : $a > $b ? 1 : -1;
});
Output
Array
(
[0] => Array
(
[latitude] => 10
[distance] => 5
)
[1] => Array
(
[latitude] => 5
[distance] => 10
)
[2] => Array
(
[latitude] => 56
[distance] => 48
)
)
See this demonstration at ideone.com
Alternatively, you could try to find a SortedArray implementation that would keep the elements sorted as they were inserted to the array, rather than only when you call usort() on the array. That's a little too advanced for this question, though.
Try this after your while loop:
uasort($locationSort, 'sortBydistance');
// or uasort($locationSort, array($this, "sortBydistance")); if you are using withing a class
print_r($locationSort);
function sortBydistance($a, $b) {
if ($a['distance'] == $b['distance']) {
return 0;
}
return ($a['distance'] < $b['distance']) ? -1 : 1;
}

PHP-Sort array based on another array?

OK, I already got this question in stackoverflow but sadly it's in javascript - Javascript - sort array based on another array
and I want it in PHP
$data = array(
"item1"=>"1",
"item2"=>"3",
"item3"=>"5",
"item4"=>"2",
"item5"=>"4"
);
to match the arrangement of this array:
sortingArr = array("5","4","3","2","1");
and the output I'm looking for:
$data = array(
"item3"=>"5",
"item5"=>"4",
"item2"=>"3",
"item4"=>"2",
"item1"=>"1"
);
Any idea how this can be done?
Thanks.
For a detailed answer, why array_multisort does not match your needs, view this answer, please:
PHP array_multisort not sorting my multidimensional array as expected
In short: You want to sort an array based on a predefined order. The Answer is also given over there, but i copied one solution to this answer, too:
Use usort and array_flip, so you be able to turn your indexing array (ValueByPosition) into a PositionByValue Array.
$data = array(
"item1"=>"1",
"item2"=>"3",
"item3"=>"5",
"item4"=>"2",
"item5"=>"4"
);
usort($data, "sortByPredefinedOrder");
function sortByPredefinedOrder($leftItem, $rightItem){
$order = array("5","4","3","2","1");
$flipped = array_flip($order);
$leftPos = $flipped[$leftItem];
$rightPos = $flipped[$rightItem];
return $leftPos >= $rightPos;
}
print_r($data);
// usort: Array ( [0] => 5 [1] => 4 [2] => 3 [3] => 2 [4] => 1 )
// uasort: Array ( [item3] => 5 [item5] => 4 [item2] => 3 [item4] => 2 [item1] => 1 )
However this would require you to predict all possible items inside the predefined order array, or thread other items in an appropriate way.
If you want to maintain the assoc keys, use uasort instead of usort.
Pretty simple ?
$data = array(
"item1"=>"1",
"item2"=>"3",
"item3"=>"5",
"item4"=>"2",
"item5"=>"4"
);
$sortingArr = array("5","4","3","2","1");
$result = array(); // result array
foreach($sortingArr as $val){ // loop
$result[array_search($val, $data)] = $val; // adding values
}
print_r($result); // print results
Output:
Array
(
[item3] => 5
[item5] => 4
[item2] => 3
[item4] => 2
[item1] => 1
)
using usort() the right way i think
Sort an array by values using a user-defined comparison function
you can do as follow:
$data = array(
"item1"=>"1",
"item2"=>"3",
"item3"=>"5",
"item4"=>"2",
"item5"=>"4"
);
$sortingArr = array("5","4","3","2","1");
$keys = array_flip($sortingArr);
usort($data, function ($a, $b) use ($keys) {
return $keys[$a] > $keys[$b] ? 1 : -1;
});
print_r($data);
// Output
// Array ( [0] => 5 [1] => 4 [2] => 3 [3] => 2 [4] => 1 )
live example: https://3v4l.org/75cnu
Look at my following snippet to sort your array based on another array:
$res_arr = array();
for ($i = 0; $i < count($sortingArr); $i++) {
for ($j = 0; $j < count($data); $j++) {
if($data[$j] == $sortingArr[$i]) {
$res_arr[] = $data[$j];
break;
}
}
}
// $res_array is your sorted array now
Look at code snippet to make a multidimensional array sort in order of input
$input_format_list = [4, 1];
$data = array(
"0" => array(
"School" => array(
"id" => 1,
"name" => "ST.ANN'S HIGH SCHOOL",
)
),
"1" => array(
"School" => array(
"id" => 4,
"name" => "JYOTI VIDHYA VIHAR",
)
)
);
$result = array(); // result array
foreach($input_format_list as $key => $value){ // loop
foreach ($data as $k => $val) {
if ($data[$k]['School']['id'] === $value) {
$result[$key] = $data[$k];
}
}
}
return $result;
Take a look at array_multisort. I'm not completely sure how to use it, as I have never found a practical use for it (I prefer to use usort to clearly define my terms), but it might work for you.
<?php
$data = array(
"item1"=>"1",
"item2"=>"3",
"item3"=>"5",
"item4"=>"2",
"item5"=>"4"
);
$result=array_flip($data);
krsort($result);
$result=array_flip($result);
print_r($result);
//use rsort for the index array
$sortingArr = array("5","4","3","2","1");
print_r($sortingArr);
I'm pretty proud of my solution:
uasort($data, function($a, $b) use ($sortingArr) {
return array_search($a, $sortingArr) <=> array_search($b, $sortingArr);
});
Working example: https://3v4l.org/bbIk2
It uses uasort to maintain the key-value associations as the OP requested. (unlike #hassan's otherwise elegant solution)
It doesn't require that every element in the $data array be present in the sorting array. (like #HamZa's solution)
It's brief.
It uses the spaceship operator <=> for comparison instead of more verbose logic.
Code:
Expanding on the Answer of Andrew, if you want the undefined entries in the sorting array to appear at the end of the output array:
uasort($currentTags, function ($a, $b) use ($sortingArr) {
if (in_array($a, $sortingArr) && !in_array($b, $sortingArr)) return -1;
if (!in_array($a, $sortingArr) && in_array($b, $sortingArr)) return 1;
if (!in_array($b, $sortingArr)) return -1;
return array_search($a, $sortingArr) <=> array_search($b, $sortingArr);
});

Pad short input arrays with their last element value to ensure equal lengths and transpose arrays into one array

I have the following Arrays:
$front = array("front_first","front_second");
$inside = array("inside_first", "inside_second", "inside_third");
$back = array("back_first", "back_second", "back_third","back_fourth");
what I need to do is combine it so that an output would look like this for the above situation. The output order is always to put them in order back, front, inside:
$final = array(
"back_first",
"front_first",
"inside_first",
"back_second",
"front_second",
"inside_second",
"back_third",
"front_second",
"inside_third",
"back_fourth",
"front_second",
"inside_third"
);
So basically it looks at the three arrays, and whichever array has less values it will reuse the last value multiple times until it loops through the remaining keys in the longer arrays.
Is there a way to do this?
$front = array("front_first","front_second");
$inside = array("inside_first", "inside_second", "inside_third");
$back = array("back_first", "back_second", "back_third","back_fourth");
function foo() {
$args = func_get_args();
$max = max(array_map('sizeof', $args)); // credits to hakre ;)
$result = array();
for ($i = 0; $i < $max; $i += 1) {
foreach ($args as $arg) {
$result[] = isset($arg[$i]) ? $arg[$i] : end($arg);
}
}
return $result;
}
$final = foo($back, $front, $inside);
print_r($final);
demo: http://codepad.viper-7.com/RFmGYW
Demo
http://codepad.viper-7.com/xpwGha
PHP
$front = array("front_first", "front_second");
$inside = array("inside_first", "inside_second", "inside_third");
$back = array("back_first", "back_second", "back_third", "back_fourth");
$combined = array_map("callback", $back, $front, $inside);
$lastf = "";
$lasti = "";
$lastb = "";
function callback($arrb, $arrf, $arri) {
global $lastf, $lasti, $lastb;
$lastf = isset($arrf) ? $arrf : $lastf;
$lasti = isset($arri) ? $arri : $lasti;
$lastb = isset($arrb) ? $arrb : $lastb;
return array($lastb, $lastf, $lasti);
}
$final = array();
foreach ($combined as $k => $v) {
$final = array_merge($final, $v);
}
print_r($final);
Output
Array
(
[0] => back_first
[1] => front_first
[2] => inside_first
[3] => back_second
[4] => front_second
[5] => inside_second
[6] => back_third
[7] => front_second
[8] => inside_third
[9] => back_fourth
[10] => front_second
[11] => inside_third
)
Spreading the column data from multiple arrays with array_map() is an easy/convenient way to tranpose data. It will pass a full array of elements from the input arrays and maintain value position by assigning null values where elements were missing.
Within the custom callback, declare a static cache of the previously transposed row. Iterate the new transposed row of data and replace any null values with the previous rows respective element.
After transposing the data, call array_merge(...$the_transposed_data) to flatten the results.
Code: (Demo)
$front = ["front_first", "front_second"];
$inside = ["inside_first", "inside_second", "inside_third"];
$back = ["back_first", "back_second", "back_third", "back_fourth"];
var_export(
array_merge(
...array_map(
function(...$cols) {
static $lastSet;
foreach ($cols as $i => &$v) {
$v ??= $lastSet[$i];
}
$lastSet = $cols;
return $cols;
},
$back,
$front,
$inside
)
)
);
Output:
array (
0 => 'back_first',
1 => 'front_first',
2 => 'inside_first',
3 => 'back_second',
4 => 'front_second',
5 => 'inside_second',
6 => 'back_third',
7 => 'front_second',
8 => 'inside_third',
9 => 'back_fourth',
10 => 'front_second',
11 => 'inside_third',
)

php find max in 2 dimension array but return another element('id') of the array

here is my array sample $data[][];
Array( [0] => Array ( [id] => 1349
[rating1] => 1.9378838029981E-7
[rating2] => 1.1801796607774 )
[1] => Array ( [id] => 1350
[rating1] => 5.5499981876923E-7
[rating2] => 1.5121329727308 )
[2] => Array ( [id] => 1377
[rating1] => 0.00023952225410117
[rating2] => 2.1947077830236 )
[3] => Array ( [id] => 1378
[rating1] => 0.00022982302863634
[rating2] => 2.2135588326622 )
[4] => Array ( [id] => 1379
[rating1] => 0.00026272979843585
[rating2] => 2.2388295595073 )
[5] => Array ( [id] => 1380
[rating1] => 0.0002788640872546
[rating2] => 2.1815325502993 )
)
I want to find max($data[][rating?]) but return $data[id][max_rating?] i.e. id associated with the max value.
Finding the max was easy for one particular, say rating1, I used array_reduce as follows (this is inspired from this SO ):
$max = array_reduce($data, function($a, $b) {
return $a > $b['rating1'] ? $a : $b['rating1'];
});
Now I have two questions :
1. How can I extend above array_reduce to include rating2 ? I have other ratingX as well.
2. I do not want the max value, rather the $data[][id] associated with the max.
I am not so much concerned about Q1, but the second one is important as I don't want to search through the array again to get associated $data[][id].
One line of thought is to use array_map instead of array_reduce, but I couldn't come up with a version which will pass on both [id] and [rating?]. Also, there are complications when I try to max() multiple rating? at one shot, as each rating will have different max, which in turn associates with different [id].
EDIT : Just to be clear, I want all the respective ids associated with respective max of each rating?
assuming your array is unsorted you have to loop through it at least once (either manually or using builtin functions). i'd use the following code:
$array = array(
array( 'id' => 1349, 'sudhi_rating1' => 1.9378838029981E-7, 'sudhi_rating2' => 1.1801796607774 ),
array( /* … */ ),
/* … */
);
$idx_max = 0;
foreach($array as $idx => $item) {
if($item['sudhi_rating1'] > $array[$idx_max]['sudhi_rating1'])
$idx_max = $idx;
}
echo "Rating 1 has max value at id ", htmlspecialchars($array[$idx_max]['id']);
you can extend the code to check multiple ratings at once (make $idx_max an array itself and add more ifs):
$idx_max = array (
'sudhi_rating1' => 0,
'sudhi_rating2' => 0,
/* … */ );
foreach($array as $idx => $item) {
foreach($idx_max as $rating => &$max) {
if($item[$rating] > $array[$max][$rating])
$max = $idx;
}
}
foreach($idx_max as $rating => $max)
echo 'Max value for ', htmlspecialchars($rating), ' has id ', htmlspeciachars($array[$max]['id']);
$max = array_reduce($data, function($a, $b) {
if (is_null($a)) return $b;
return max($a['rating1'],$a['rating2'])>max($b['rating1'],$b['rating2']) ? $a : $b;
});
Result: no Entries $max= NULL otherwise $max['id'] is the id with the max rating
Alternatively this generic code
$max = array_reduce($data, function($a, $b) {
if (is_null($a)) return $b;
return maxRating($a)>maxRating($b) ? $a : $b;
});
function maxRating($row){
return (max(array_intersect_key($row,array_flip(array_filter(array_keys($row),function ($item) { return strstr($item,'rating')!==FALSE;})))));
}
Will find for all ratings of the form rating?
EDIT -- The code was trying to answer Q1 here is the answer for just Q2
$max = array_reduce($data, function($a, $b) {
if (is_null($a)) return $b;
return $a['rating1']>$b['rating1'] ? $a : $b;
});
EDIT2 -- This is a generic solution for any number of rating? columns
$ratingKeys=array_filter(array_keys($data[0]),function ($item) { return strstr($item,'rating')!==FALSE;});
$max = array_reduce($data,function($a,$b) use (&$ratingKeys) {
if (is_null($a)) {
$a=array();
foreach($ratingKeys as $key) {
$a[$key]=$b[$key];
$a[$key.'_id'] = $b['id'];
}
return $a;
}
foreach($ratingKeys as $key) {
if ($a[$key]<$b[$key]) {
$a[$key]=$b[$key];
$a[$key.'_id']=$b['id'];
}
}
return $a;
});
This code results in
array(4) {
["rating1"]=> float(0.0002788640872546)
["rating1_id"]=> int(1380)
["rating2"]=> float(2.2388295595073)
["rating2_id"]=> int(1379)
}
EDIT 3 -- If you change the format of the input array to use id as the array key, you can massively simplify
$max=array_reduce(array_keys($data),function ($a,$b) use (&$data) {
if (is_null($a)) $a=array();
foreach(array_keys($data[$b]) as $item) {
if (!isset($a[$item]) {
$a[$item] = $b;
} else {
if ($data[$a[$item]][$item]) < $data[$b][$item]) $a[$item]=$b;
}
return $a;
}
});
This code results in
array(2) {
["rating1"]=> int(1380)
["rating2"]=> int(1379)
}

Categories