So I'm trying to create a script in PHP that has multiple objects on a cartesian plane (xy) and to find the closest distance.
for e.g., lets say we have 3 objects on an XY plane and I want to find the 2 closest objects by distance.
Now I can use the distance formula:
http://www.mathwarehouse.com/algebra/distance_formula/index.php
However, how do I write a function on this that takes in an array of objects?
I'm thinking something like this (please advise):
-array $objects will be the list (of objects w/ names/coordinates)
-it should return array the closest object names
$object1 = ['object1' => 'a', 'x' => 1, 'y' => 1];
$object2 = ['object2' => 'b', 'x' => 1, 'y' => 3];
$object3 = ['object3' => 'c', 'x' => 5, 'y' => 5];
I'm not sure how to write this though. I've started the function:
function findClosestObject(array $objects) {
d = sqrt(pow((x2-x1),2) + pow((y2-y1)),2))
}
Put your objects in an array.
Iterate over the array.
You can save a fair number of CPU cycles by not performing the sqrt() since you don't care what the literal distance is.
eg:
function getDistanceIsh($a, $b) {
return pow(abs(($a['x']-$b['x'])), 2) + pow(abs(($a['y']-$b['y'])), 2);
}
$testPoint = ['x'=>1, 'y'=>2];
$closest = $objects[0];
$closestDist = getDistance($testpoint, $objects[0]);
for( $i=1,$c=count($objects); $i<$c; $i++ ) {
$dist = getDistance($testpoint, $objects[$i]);
if( $dist < $closestDist ) {
$closest = $objects[$i];
$closestDist = $dist;
}
}
For 3 objects, there are only 3 combinations of 2 objects - 1 + 2, 1 + 3, and 2 + 3.
Abstract your distance formula into a function :
function distance($obj1,$obj2) {
return sqrt(pow(($obj1['x']-$obj2['x']),2) + pow(($obj1['y']-$obj2['y']),2));
}
Now calculate each combination of two objects, and store the results in an array :
$dist['12'] = distance($object1,$object2) ;
$dist['13'] = distance($object1,$object3) ;
$dist['23'] = distance($object2,$object3) ;
// sort values into ascending order
asort($dist);
// take 1st element index
reset($dist);
$closest = key($dist);
echo $closest;
To do this with a larger array of objects, you're going to need to implement code which first generates all the two-object combinations.
Writing your own code for such a computationally expensive programs is actually a bad start. Always first try to see if there is any Open Source code which can perform the same task more faster, and secure way. Because those codes are verified by many professionals and will mostly be error free, and also reduces your development time. So in your case, first finding the fastest algorithm is important. Dijkstra Algorithm is widely used for finding the shortest path, and some examples of Open Source PHP code are PHP-Dijkstra and Taniko-Dijkstra
Here's a solution that allows for a variable amount of objects (needs more than 1 of course). It utilizes hypot(), which is basically the same as your distance formula.
$objects = [
[ 'name' => 'obj1', 'x' => 1, 'y' => 1 ],
[ 'name' => 'obj2', 'x' => 1, 'y' => 3 ],
[ 'name' => 'obj3', 'x' => 5, 'y' => 5 ],
[ 'name' => 'obj4', 'x' => 10, 'y' => 8 ],
[ 'name' => 'obj5', 'x' => 30, 'y' => 12 ],
[ 'name' => 'obj6', 'x' => 31, 'y' => 13 ],
[ 'name' => 'obj7', 'x' => 40, 'y' => 15 ]
];
function findClosestObjects( array $objects ) {
// initialize $minDistance to null
$minDistance = null;
// total amount of objects
$len = count( $objects );
// loop from first object to the second-to-last
// last will be handled inside nested for loop
for( $i = 0; $i < $len - 1; $i++ ) {
// set $o1 to current $i object
$o1 = $objects[ $i ];
// loop from next object to the last, to avoid duplicate pairs
for( $j = $i + 1; $j < $len; $j++ ) {
// set $o2 to current $j object
$o2 = $objects[ $j ];
// calc distance
$distance = hypot( $o2[ 'x' ] - $o1[ 'x' ], $o2[ 'y' ] - $o1[ 'y' ] );
// if $minDistance is null or $distance is smaller than $minDistance
if( $minDistance == null || $minDistance > $distance ) {
// put objects and distance in $result
$result = [ $o1, $o2, 'distance' => $distance ];
// and update $minDistance
$minDistance = $distance;
}
}
}
return $result;
}
var_dump( findClosestObjects( $objects ) );
view parsed online # eval.in
Related
I wonder if there is a faster way to sum i.e. the weight of each item by qty.
$items = [
[
'qty' => 1,
'weight' => 1,
],
[
'qty' => 2,
'weight' => 1,
],
[
'qty' => 3,
'weight' => 1,
],
];
$totalWeight = 0.0;
foreach ($items as $item) {
$totalWeight += $item['weight'] * $item['qty'];
}
echo $totalWeight . PHP_EOL;
If i would not need the qty offset, i just could use
array_sum(array_column($items, 'weight'))
But this wont work ofc in this example.
Anybody has an idea if and how this could be done faster?
Thanks
/cottton
EDIT
Test script:
$items = [];
for ($i = 0; $i < 1000; $i++) {
$items[] = [
'foo' => 1,
'qty' => $i,
'bar' => 2,
'weight' => $i,
'baz' => 3,
];
}
$totalWeight = 0.0;
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$totalWeight = 0.0;
foreach ($items as $item) {
$totalWeight += $item['weight'] * $item['qty'];
}
}
$elapsed = sprintf('%f', microtime(true) - $start);
echo "Elapsed: {$elapsed}\r\n";
echo "Total weight: {$totalWeight}\r\n";
// Elapsed: 0.744311
// Total weight: 332833500
Use https://www.w3schools.com/php/func_array_reduce.asp
<?php
$items = [
[
'qty' => 1,
'weight' => 1,
],
[
'qty' => 2,
'weight' => 1,
],
[
'qty' => 3,
'weight' => 1,
],
];
$totalWeight = array_reduce($items,
function($acc, $e) { return $acc + ($e['weight'] * $e['qty']); });
echo $totalWeight . PHP_EOL;
?>
You can map array_product over the inner arrays and array_sum the resulting array of products.
$totalWeight = array_sum(array_map('array_product', $items));
(This may not work if the example you showed was simplified for the the question here and the inner arrays include other fields that aren't shown.)
Here is a method that loops the unique weights and finds the sum of qty of each weights and multiply it with the weight.
In your case it makes one loop.
$weights = array_column($items, 'weight');
$result =0;
Foreach(array_unique($weights) as $weight){
// Finds the items with the weight 1 for example
$weightitems = preg_grep("/". $weight . "/", $weights);
// Sum quantity of items with the weight of 1 and multiply with weight (1).
$result += array_sum(array_column(array_intersect_key($items, $weightitems), "qty")) * $weight;
}
Echo $result;
Notice I added a "5" just to see that it worked correct in my example code.
https://3v4l.org/V293P
A slightly more optimized code, but still not faster than a simple loop as far as I can see.
I can make it 4-ish times slower depending on the input but I can't get it closer than that.
$weights =array_unique(array_column($items, 'weight'));
$result =[];
Foreach($weights as $weight){
$weightitems = array_filter($items, function ($var) use ($weight) {
return ($var['weight'] == $weight);
});
$result[] = array_sum(array_column($weightitems, "qty")) * $weight;
}
Echo array_sum($result) ."\n";
https://3v4l.org/aB7d9
How accurate does it have to be?
This is faster than looping but is off by 1 on 3000 items and a total of 17992.
Is that within error margin, then this is faster.
I calculate the average of the sumproduct of the two columns using array_sum, array_column and count.
$count = count($items);
$weights = array_sum(array_column($items, 'weight'));
$qty = array_sum(array_column($items, 'qty'));
Echo ($qty * $weights)/$count ."\n";
// 17991 (should be 17992)
https://3v4l.org/c3CIg
I think I have tried all possible solutions now.
I need to calculate the average value for each column of data in an array of associative arrays. The result should be a flat, associative array of averages.
Sample array:
$array = [
[
"a" => 0.333,
"b" => 0.730,
"c" => 0.393
],
[
"a" => 0.323,
"b" => 0.454,
"c" => 0.987
],
[
"a" => 0.753,
"b" => 0.983,
"c" => 0.123
]
];
I am looking for a simpler way of processing all the array elements and producing a single array which has a mean value (average) of all the corresponding values.
My current code works, but I'm hoping for a more elegant approach.
$a = []; // Store all a values
$b = []; // Store all b values
$c = []; // Store all c values
for ( $i = 0; $i < count( $array ); $i ++ ) {
// For each array, store each value in it's corresponsing array
// Using variable variables to make it easy
foreach ( $array[ $i ] AS $key => $val ) {
$k = $key;
$$k[] = $val;
};
}
// Create single array with average of all
$fa = array(
'a' => array_sum($a) / count($a),
'b' => array_sum($b) / count($b),
'c' => array_sum($c) / count($c)
);
The desired result:
[
'a' => 0.4696666666666667,
'b' => 0.7223333333333333,
'c' => 0.501,
]
Assuming each sub-array has the same keys:
foreach(array_keys($array[0]) as $key) {
$result[$key] = array_sum($tmp = array_column($array, $key))/count($tmp);
}
Get the keys from the first sub-array
Loop, extract those values from the main array and calculate
Here is a functional-style approach which will work as #AbraCadaver's answer does.
Code: (Demo)
var_export(
array_reduce(
array_keys($array[0] ?? []),
fn($result, $k) => $result + [$k => array_sum($c = array_column($array, $k)) / count($c)],
[]
)
);
I used ?? [] as a safeguard for live applications where it is possible that the input array is empty (and does not contain an element at [0]).
Using PHP7.4's arrow function syntax allows the accessibility of the input array without a use() statement.
The + in the custom function is not performing addition; it is uniting the result array with the new associative array -- this serves to push the new element into the returned result array.
This question already has answers here:
Group 2d array data by one column and sum other columns in each group (separately)
(4 answers)
Closed last month.
I have 2 arrays:
$array1 = [
['amount' => 21600.00, 'rows' => 2, 'student_id' => 1],
];
$array2 = [
['amount' => 541990.00, 'rows' => 512, 'student_id' => 1],
['amount' => 347480.00, 'rows' => 281, 'student_id' => 2],
['amount' => 507400.00, 'rows' => 214, 'student_id' => 3],
];
I want to merge both arrays based on the same student_id value. When a student_id is found in both arrays, I want to sum the two amount values and the two rows values.
Expected result:
array (
0 =>
array (
'amount' => 563590.0,
'rows' => 514,
'student_id' => 1,
),
1 =>
array (
'amount' => 347480.0,
'rows' => 281,
'student_id' => 2,
),
2 =>
array (
'amount' => 507400.0,
'rows' => 214,
'student_id' => 3,
),
)
I have tried using array_merge() and array_merge_recursive() but they didn't give me the expected result.
Here is simple code to solve your problem,
count($arr) > count($arr0) ? ($greater = $arr AND $smaller = $arr0) : ($smaller = $arr AND $greater = $arr0);
foreach ($greater as $key => &$value) {
foreach ($smaller as $key1 => &$value1) {
if($value['student_id'] == $value1['student_id']){
$value['amount'] +=$value1['amount'];
$value['rows'] +=$value1['rows'];
}
}
}
Check output here
The comment about combining the queries is probably the better approach.
SELECT student_id, sum(amount) as amount, sum(rows) as rows
from students
group by student_id;
However, if you can't do it via a query, PHP doesn't natively add array values together (it doesn't make sense)
Here's what you'd have to do
function arrayMergeMatch(&$a, &$b)
{
foreach ($a as $key => $value) {
if (array_key_exists($key, $b)) {
// there's a matching index in both a & b
$a[$key] = [
$a[$key]['amount'] += $b[$key]['amount'],
$a[$key]['rows'] += $b[$key]['rows'],
$a[$key]['student_id'],
];
}
}
}
$firstArray = $secondArray = [];
$firstArray[0] = [
'amount' => 21600.00,
'rows' => 2,
'student_id' => 1
];
$secondArray[0] = [
'amount' => 541990.00,
'rows' => 512,
'student_id' => 1,
];
$secondArray[1] = [
'amount' => 347480.00,
'rows' => 281,
'student_id' => 2,
];
$secondArray[2] = [
'amount' => 507400.00,
'rows' => 214,
'student_id' => 3,
];
$firstArrayLength = count($x);
$secondArrayLength = count($y);
$combinedArray = null;
if ($firstArrayLength > $secondArrayLength) {
// loop through x checking to see if there's a matching y
arrayMergeMatch($firstArray, $secondArray);
$combinedArray = $firstArray;
}
else {
// y is longer than or equal to x, so loop through y, looking for matching
// index in x
arrayMergeMatch($secondArray, $firstArray);
$combinedArray = $secondArray;
}
print_r($combinedArray);
Nested loops are not necessary. There will be several different styles, but the crux of the task is to create a result array that temporarily serves as a lookup array while you iterate the incoming data.
To do this, assign temporary keys using student_id values. When a student_id is encountered after it is found in the result array, just add the values to the stored related values in the group. When finished iterating, re-index the result with array_values().
Functional style: (Demo1) (Demo2 - likely slower)
var_export(
array_values(
array_reduce(
$array2,
function($result, $row) {
if (!isset($result[$row['student_id']])) {
$result[$row['student_id']] = $row;
} else {
$result[$row['student_id']]['amount'] += $row['amount'];
$result[$row['student_id']]['rows'] += $row['rows'];
}
return $result;
},
array_column($array1, null, 'student_id')
)
)
);
Language construct loop: (Demo1) (Demo2 - likely slower)
$result = array_column($array1, null, 'student_id');
foreach ($array2 as $row) {
if (!isset($result[$row['student_id']])) {
$result[$row['student_id']] = $row;
} else {
$result[$row['student_id']]['amount'] += $row['amount'];
$result[$row['student_id']]['rows'] += $row['rows'];
}
}
var_export(array_values($result));
All of the above snippets should be used instead of the other posted answers because they will never iterate more than m + n. Here's a critique of the other answers:
Rahul's answer is using a nested loop -- this means the number of iterations will be m*n (number of rows in array1 multiplied by number of rows in array2) -- this is very indirect/inefficient. Even if a break was used, it still wouldn't be as direct/efficient as my snippets.
Kearney's answer is completely ignoring the requirement to sort on student_id values (even after the $x and $y typos are fixed). This php-based answer is simply incorrect. Proof. Less importantly, $b does not need to be made modifiable by reference.
It seems that a large, complicated codebase depends on the order arsort produces. Before I dive in to discern what's actually happening in like 50 classes -- is there a simple way to shuffle items with equal values?
In other words, if the input is
['foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2]
I'd like to get
['baz' => 3, 'bar' => 3, 'foo' => 3, 'this' => 2, 'that' => 2]
one run maybe, then
['baz' => 3, 'bar' => 3, 'foo' => 3, 'that' => 2, 'this' => 2]
on another random run.
How about something like this? (Untested)
Worst Case complexity: O(k)
Note: Written for algorithmic clarity and not PHP details...
function shuffleInput( $data ) {
// Separate into sets.
$sets = [];
foreach ( $data as $k => $v ) {
$sets[$v][] = $k;
}
// Shuffle & Join.
$data = [];
foreach ( $sets as $v => &$set ) {
shuffle( $set );
foreach( $set as $k ) {
$data[$k] = $v;
}
}
return $data;
}
Depending on the size of your input, it might be a better idea to unset every element in $data in the first loop instead of just creating a new array. This applies if data is very large and memory is precious to you - as well as reducing any sudden spikes & dips in memory usage.
Also, if you're going to continously shuffle the same $data around you might want to separate out the making of $sets to some other place or at least allow the developer to pass/get it as a side effect.
If you do not want to deal with shuffle, but rather prefer to check all permutations of the array, then you can do something like this:
$arr = array('foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2);
$keys = array_keys($arr);
$indexes = range(0, count($arr) - 1);
pc_permute($indexes, $perms);
var_dump($perms);
function pc_permute($items, &$ret = array(), $perms = array( )) {
if (empty($items)) {
$ret[] = $perms;
} else {
for ($i = count($items) - 1; $i >= 0; --$i) {
$newitems = $items;
$newperms = $perms;
list($foo) = array_splice($newitems, $i, 1);
array_unshift($newperms, $foo);
pc_permute($newitems, $ret, $newperms);
}
}
}
Array $perms will give all permutations of the indexes, key name by index you can get from $keys and value by key or index (use array_slice) from $arr :)
ps: but you should understand - more elements you have in the original array, more permutations you will find. if there are n elements then there will be n! permutations. for n = 5 there are 120 permutations.
I got array of points:
array(
array('x' => ...,'y' => ...),
array('x' => ...,'y' => ...)
...
)
What is the best way to make those points one, that is in "avarge" position? Is pairing and then pairing and then pairing... a good algorithm?
I would give myself -1 for this question, as it seems to be really easy, but I am working on project for more than 20 hours now, and my brain went off.
Hmm... is it as simple as counting avarge x and avarge y?
The best solution would be to turn your computer off and go to sleep for a couple of good hours. Then wake up rested and ready for a new programming session. This solution in based on an assumption that those 20 hours you have assigned this project was without any proper breaks.
While this isn't a direct answer to your question, it will certainly help you get there by yourself. Don't underestimate the power of a nap.
Take a look at this function I wrote to loop through all the elements of your multidimensional array and return their average after adding all of the elements together.
print_r(getAveragePoints(array(array('x' => 1,'y' => 3),array('x' => 2,'y' => 4))));
function getAveragePoints($arrays = array()) {
if(!empty($arrays)) {
$i=0;
$x = 0;
$y = 0;
foreach($arrays as $array) {
// this would take avg
$x += $array['x']; // x
$y += $array['y']; // y
$i++;
}
$avgX = $x / $i;
$avgY = $y / $i;
return array($avgX,$avgY);
} else {
return array(0,0);
}
}
Simple average option:
$pointArray = array(
array('x' => 1,'y' => 2),
array('x' => 2,'y' => 5),
array('x' => 3,'y' => 3) ,
array('x' => 4,'y' => 6) ,
array('x' => 4,'y' => 5) ,
);
$valueCount = count($pointArray);
$midpoint = array_map(
function($value) use($valueCount) {
return $value / $valueCount;
},
array_reduce(
$pointArray,
function($summary, $value) {
return array(
'x' => $summary['x'] += $value['x'],
'y' => $summary['y'] += $value['y']
);
},
array('x' => 0, 'y' => 0)
)
);
var_dump($midpoint);