Running calculations on multi-dimensional arrays? - php

This is probably a real simple question but I'm looking for the most memory efficient way of finding out data on a particular multi-dimensional array.
An example of the array:
[0] => Array(
[fin] => 2
[calc] => 23.34
[pos] => 6665
)
[1] => Array(
[fin] => 1
[calc] => 25.14
[pos] => 4543
)
[2] => Array(
[fin] => 7
[calc] => 21.45
[pos] => 4665
)
I need a method of identifying the values of the following things:
The max 'calc'
The min 'calc'
The max 'pos'
The min 'pos'
(you get the gist)
The only way I can think of is manually looping through each value and adjusting an integer so for example:
function find_lowest_calc($arr) {
$int = null;
foreach($arr['calc'] as $value) {
if($int == null || $int > $value) {
$int = $value;
}
}
return $int;
}
The obvious drawbacks of a method like this is I would have to create a new function for each value in the array (or at least implement a paramater to change the array key) and it will slow up the app by looping through the whole array 3 or more times just to get the values. The original array could have over a hundred values.
I would assume that there would be an internal function to gather all of (for example) the 'calc' values into a temporary single array so I could use the max function on it.
Any ideas?
Dan

$input = array(
array(
'fin' => 2
'calc' => 23.34
'pos' => 6665
),
array(
'fin' => 1
'calc' => 25.14
'pos' => 4543
),
array(
'fin' => 7
'calc' => 21.45
'pos' => 4665
)
);
$output = array(
'fin' => array(),
'calc' => array(),
'pos' => array(),
);
foreach ( $input as $data ) {
$output['fin'][] = $data['fin'];
$output['calc'][] = $data['calc'];
$output['pos'][] = $data['pos'];
}
max($output['fin']); // max fin
max($output['calc']); // max calc
min($output['fin']); // min fin

There is no way to speed that up, besides calculating all three values at once. The reason for this is that you always need to loop through the array to find the sub-arrays. Even if you find a bultin function, the time complexity will be the same. The only way to make a real difference is by using another datastructure (i.e, not any of the bultin ones, but one you write yourself).

How are you receiving the array? If it is your code which is creating the array, you could calculate the minimum and maximum values as you are reading in the data values:
$minCalc = null;
$arr = array();
for(...){
//read in 'calc' value
$subArr = array();
$subArr['calc'] = //...
if ($minCalc === null || $minCalc > $subArr['calc']){
$minCalc = $subArr['calc'];
}
//read in other values
//...
$arr[] = $subArr;
}
Also, in your find_lowest_calc function, you should use the triple equals operator (===) to determine whether the $int variable is null. This is because the statement $int == null will also return true if $int equals 0, because null equals 0 when converted to an integer.

You don't have to crate a new function for each value, just pass the key you want in the function
function find_lowest($arr, $indexkey) {
$int = null;
foreach($arr[$indexkey] as $value) {
if($int == null || $int > $value) {
$int = $value;
}
}
return $int;
}
As php is not type-safe, you should be fine passing both string or int index

Related

Replacing the unusual number in php array

I have the array of numbers in php like following
[2021-06-04] => Array
(
[09:15:04] => 1.51
[09:15:27] => 1.32
[09:15:46] => 14.29
[09:16:05] => 14.03
[09:18:05] => 13.97
[09:18:24] => 13.88
[09:18:42] => 13.94
[09:19:01] => 13.80
[09:19:05] => 13.86
[09:19:23] => 13.97
[09:19:41] => 6.99
[09:20:00] => 14.14
[09:20:06] => 14.08
[09:20:25] => 14.04
)
In above array items .. 1.51,1.32 and 6.99 is somewhat off , so i want to replace then with next element so it result array should look like below.
[2021-06-04] => Array
(
[09:15:04] => 14.29
[09:15:27] => 14.29
[09:15:46] => 14.29
[09:16:05] => 14.03
[09:18:05] => 13.97
[09:18:24] => 13.88
[09:18:42] => 13.94
[09:19:01] => 13.80
[09:19:05] => 13.86
[09:19:23] => 13.97
[09:19:41] => 14.14
[09:20:00] => 14.14
[09:20:06] => 14.08
[09:20:25] => 14.04
)
I can get array average using
$a = array_filter($a);
$average = array_sum($a)/count($a);
echo $average;
and I tried something like below
foreach($arr as $key=>$val)
{
if($avg-$val) > 5)
{
$arr[$key] = $nextactualelem; // How do I get next correct element whose $avg-$val is less than 5
}
}
P.s : hi , is it possible to get next 10 elements average.. instead of total average.. because in larger sets its not working fine.. because larger data sets ranging from 10 to 30... so difference of 5 is not working on the initial smaller values.or is there any other faster way to do it like difference between next and previous number..
If you're going to make changes to the array you're working on foreach is not your best choice. referring to keys is troublesome, variable values ale not clean on every run. Generally if you need to do something with the array you're iterating use a for loop instead.
Now since you're going to replace the values, we have a slight issue. Looking at edge cases. What if we want to go over the array in reverse (that makes sense since we want to replace an anomaly with the next value) but the last value is an anomaly? Still reverse order is a good choice but we need to make sure that at least the last value is a valid one.
Given the fact that the keys are rather arbitrary we can't use a for loop at this point. Thankfully we can use the internal array pointer to move around.
$arr = [
'09:15:04' => 1.51,
'09:15:27' => 1.32,
'09:15:46' => 14.29,
'09:16:05' => 14.03,
'09:18:05' => 13.97,
'09:18:24' => 13.88,
'09:18:42' => 13.94,
'09:19:01' => 13.80,
'09:19:05' => 13.86,
'09:19:23' => 13.97,
'09:19:41' => 6.99,
'09:20:00' => 14.14,
'09:20:06' => 14.08,
'09:20:25' => 14.04
];
$average = array_sum($arr)/count($arr);
end($arr); // We move the pointer to the end of the array;
//... do some verification magic here
// so we have a valid starting value on the end of the array
while(prev($arr) != false) { // move one step back while we have values
if (($average - current($arr)) > 5) {
$key = key($arr); // Grab the current key
$arr[$key] = next($arr); // replace the value with the next
prev($arr); // put the pointer back in the right place
}
}
var_dump($arr);
Note that if you run this the 6.99 will remain in the array as it's only 4.71 away from the average. So some tweaks to your conditions might be in order.
EDIT:
taking the new information We could try something along the lines of:
$lookup[] = end($arr); // We move the pointer to the end of the array and place it in a lookup array
//... do some varification magic here so we have a valid starting value on the end of the array
while(prev($arr) !== false) { // move one step back while we have values
$average = array_sum($lookup)/count($lookup); // calculate the current average based on lookup
if (abs($average - current($arr)) > 2) { // if the difference from the current average is more the 2 points
$arr[key($arr)] = next($arr); // replace the value with the next
prev($arr); // put the pointer back in the right place
}
$lookup[] = current($arr); // add the current value to the average
if (count($lookup) > 10) { // if we have more then 10 values in the lookup
array_shift($lookup); // Shift an element off the beginning of array
}
}
This should smooth things out ;)
loop through the reverse of your array, saving the last value, and replace the odd values for the last value that you saved before.
UPDATE:
Now $avg is the average of the next 10 available values.
$arr = [
'09:15:04' => 1.51,
'09:15:27' => 1.32,
'09:15:46' => 14.29,
'09:16:05' => 14.03,
'09:18:05' => 13.97,
'09:18:24' => 13.88,
'09:18:42' => 13.94,
'09:19:01' => 13.80,
'09:19:05' => 13.86,
'09:19:23' => 13.97,
'09:19:41' => 6.99,
'09:20:00' => 14.14,
'09:20:06' => 14.08,
'09:20:25' => 14.04,
];
$avg = 0;
$inverseArr = array_reverse($arr);
$lastValue = false;
$offset = -1;
foreach ($inverseArr as $key => $val) {
if (($avg - $val) > 5 && $lastValue !== false) {
$arr[$key] = $lastValue;
}
else {
$lastValue = $val;
}
$nextTen = array_slice($arr, $offset, 10);
$avg = array_sum($nextTen) / count($nextTen);
$offset--;
}
// array (size=14)
// '09:15:04' => float 14.29
// '09:15:27' => float 14.29
// '09:15:46' => float 14.29
// '09:16:05' => float 14.03
// '09:18:05' => float 13.97
// '09:18:24' => float 13.88
// '09:18:42' => float 13.94
// '09:19:01' => float 13.8
// '09:19:05' => float 13.86
// '09:19:23' => float 13.97
// '09:19:41' => float 14.14
// '09:20:00' => float 14.14
// '09:20:06' => float 14.08
// '09:20:25' => float 14.04

Simplify PHP array with same items

I have this PHP array:
$this->user_list = array( 0 => 'Not paid',1 => 'Not paid', 2 => 'Not paid', 7 => 'Waiting, 15 => 'Waiting', 10 => 'Cancelled' );
How can I simplify this array as the id numbers are different, but some of them have same status?
I tried it like this:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
but it doesn't work as expected.
Basically I want to achieve this:
echo $this->user_list[15] should give me Waiting, echo $this->user_list[10] should give me Cancelled, etc. So this is working in my first array very well, I am just thinking about grouping duplicate names there.
As mentioned by other contributors, there is no native support in the PHP grammar for your intended use case. As clearly stated in the PHP: Arrays documentation:
An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments.
So basically each element in an array is a key => value pair, which means you cannot associate multiple keys to a single element.
This also explains why your first tentative didn't work:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
If you don't specify a key for an element, PHP uses a progressive index (0, 1, ...). So basically in the example above, the first zero is not actually a key, but a value, and PHP binds it to the key = 0. Maybe it could be easier for you to understand how it works if you print a var_dump or print_r of $this->user_list. You would get something similar to the following structure (NOTE: I have simplified the structure to make it more clear):
[
0 => [
0 => 0
1 => 1
2 => "Not paid"
],
1 => [
0 => 7,
15 => "Waiting"
],
10 => "Cancelled"
]
So how do we resolve this problem? Well... actually there is no need to contort the structure by swapping keys with values as other contributors seem to suggest. Changing the structure might simplify your "data entry" work but might also create big issues in other parts of the program because who knows, maybe accessing the invoice data by "ID" is simply more efficient than by "status" ... or something.
Since PHP does not provide such a feature out of the box, I believe a better solution would be to develop our own function; a good starting point could be the one in the example below.
function explode_array($config, $sep = ',') {
$res = [];
foreach($config as $configKey => $value) {
// split key values
$keys = explode($sep, $configKey);
foreach($keys as $key) {
$res[$key] = $value;
}
}
return $res;
}
$config = [
'0,1,2' => 'Not paid',
'7,15' => 'Waiting',
'10' => 'Cancelled'
];
$myArr = explode_array($config);
print_r($myArr);
The idea is quite simple: since we cannot use an array as key we leverage the next best data type, that is a CSV string. Please note there is no error handling in the above code, so the first thing you may want to do is adding some validation code to the explode_array (or however you wish to name it) function.
you should use like this. if id number is invoice id or something else and other value is there status about it.
$arr = array(
'Not paid' => [0,1,2] ,
'Waiting' => [5,6],
'Cancelled' =>[8]
);
foreach($arr as $key => $val){
foreach($val as $keys => $vals){
echo "invoiceid ".$vals ." status ".$key;
echo"<br>";
}
}
// for only one status you can use like this
foreach($arr['Not paid'] as $key => $val){
echo $val;
echo"<br>";
}
just try to run this and check output.
PHP has no built-in function or structure for handling cases like this. I'd use a simple array value-cloning function to map your duplicates. Simply have one instance of each status, then map the aliases, and then run a function that clones them in. As follows:
// Status list:
$ulist = [ 0 => 'Not paid', 7 => 'Waiting', 10 => 'Cancelled' ];
// Alternative IDs list, mapped to above source IDs:
$aliases = [ 0 => [1,2], 7 => [15] ];
// Function to clone array values:
function clone_values(array &$arr, array $aliases)
{
foreach($aliases as $src => $tgts) {
foreach($tgts as $tgt) {
$arr[$tgt] = $arr[$src];
}
}
ksort($arr); // If the order matters
}
// Let's clone:
clone_values($ulist, $aliases);
This results in the following array:
array(6) {
[0] · string(8) "Not paid"
[1] · string(8) "Not paid"
[2] · string(8) "Not paid"
[7] · string(7) "Waiting"
[10] · string(9) "Cancelled"
[15] · string(7) "Waiting"
}
....which can be accessed as you expect, here $ulist[2] => Not paid, etc. If the use case is as simple as illustrated in the OP, I'd personally just spell it out as is. There's no dramatic complexity to it. However, if you have dozens of aliases, mapping and cloning begins to make sense.
As said in the comments, you can't have multiple keys with one value. The best way is to use the keyword => [ number, number, number...] construction.
//set a result array
$result = [];
//loop the original array
foreach ( $this->user_list as $number => $keyword ){
//if the keyword doesn't exist in the result, create one
if(!isset ( $result [ $keyword ] ) ) $result[ $keyword ] = [];
//add the number to the keyword-array
$result[ $keyword ] [] = $number;
}

Intersection of two multidimensional arrays in PHP

I am having the following array defined:
array(
'name'=>'Blue',
'age'=>'0',
'skin'=>array(
'White Skin','Tanned Skin'
),
'eye'=>array(
'Black','Brown','Honey'
),
'personality'=>array(
'Intelligent','Warm','Trustworthy','Sweet'
),
'ocassion'=>array(
'Every day wear','Celebrations','Restaurant Dinner','Feasts','Visiting friends'
),
'hair'=>'All Colors',
'style'=>array(
'Loved to be admired','Center of attention'
),
'description'=>'Blue lens are perfect for any..'
);
and I am trying to find the number of matches, from an HTML form into this array. A possible return from the HTML form, in array format would be:
Array
(
[age] => 16
[skin] => Tanned Skin
[eye] => Brown
[personality] => Array
(
[0] => Intelligent
[1] => Warm
[2] => Trustworthy
)
[ocassion] => Weddings
[hair] => Dark Brown
[style] => Array
(
[0] => Style Queen
[1] => Testing val
)
)
I have tried iterating trough each key of the first array, but failed to achieve what I want, and also I've tried using the function array_intersect_assoc($stack,$search) but it seems it won't find the exact matches because the $search array ( second example ) has some key=>value pairs that are of type string, and it cannot match any occurrence into the first array because the value is actually an array, and not a string.
Can someone point me an idea or can let me know what's best to do over here?
I have tried a lot of things in the last 3 hours but no success.
Ok, so how about this.
The source data:
$demands = array(
'name'=>'Blue',
'age'=>'0',
'skin'=>array(
'White Skin','Tanned Skin'
),
'eye'=>array(
'Black','Brown','Honey'
),
'personality'=>array(
'Intelligent','Warm','Trustworthy','Sweet'
),
'ocassion'=>array(
'Every day wear','Celebrations','Restaurant Dinner','Feasts','Visiting friends'
),
'hair'=>'All Colors',
'style'=>array(
'Loved to be admired','Center of attention'
),
'description'=>'Blue lens are perfect for any..'
);
$possible_match = array(
'age'=>'16',
'skin'=>'Tanned Skin',
'eye'=>'Brown',
'personality'=>array(
'Intelligent','Warm','Trustworthy'
),
'ocassion'=>array(
'Weddings'
),
'hair'=>'Dark Brown',
'style'=>array(
'Style Queen','Testing value'
)
);
And the match-making algorithm:
$result = array();
$count_matches = 0;
// Go through all the demands
foreach ($demands as $key => $value){
// If there's a matching key in the possible match array
if (isset($possible_match[$key])){
// If there are more demanded values
if (is_array($value)){
// Let all demanded values be lowercase
$value = array_map('strtolower', $value);
// If there are more possible matching values
if (is_array($possible_match[$key])){
// Let all possibly matching values be lowercase, too
$possible_match[$key] = array_map('strtolower', $possible_match[$key]);
// And then do the intersect.
$intersect = array_intersect($value, $possible_match[$key]);
if ($intersect){
// If that intersect is not empty, add that to the resulting array
$result[$key] = $intersect;
$count_matches += count($intersect);
};
} else {
// If there's only one possible matching value, search that
// value in the demaned array
if (in_array(strtolower($possible_match[$key]), $value, true)){
// And add it to the results
$result[$key][] = strtolower($possible_match[$key]);
$count_matches++;
}
}
} else {
if (is_array($possible_match[$key])){
// If there are more possible matching values but the demand is a string,
// find that string in those possible values
$possible_match[$key] = array_map('strtolower', $possible_match[$key]);
if (in_array(strtolower($value), $possible_match[$key], true)){
// And add it to the results
$result[$key] = $value;
$count_matches++;
}
} else {
// If the demanded value is only one (= it's a string and not an array)
// and the possible match is also a string, do a lowercase compare
// + if there's a word "all" in the demanded value, pass it at all times ;D
if (strtolower($possible_match[$key]) == strtolower($value)
|| stripos($value, "all") !== false){
// And add it to the resulting array
$result[$key] = strtolower($value);
$count_matches++;
}
}
}
}
}
var_dump ($result);
var_dump ($count_matches);
There may be some opportunities for optimizing, but the basic idea should be there :)
The result:
array (size=4)
'skin' =>
array (size=1)
0 => string 'tanned skin' (length=11)
'eye' =>
array (size=1)
0 => string 'brown' (length=5)
'personality' =>
array (size=3)
0 => string 'intelligent' (length=11)
1 => string 'warm' (length=4)
2 => string 'trustworthy' (length=11)
'hair' => string 'all colors' (length=10)
Plus the count, if you'd like:
int 6

Understanding the basics of multidimensional arrays

I am new to using multidimensional arrays with php, I have tried to stay away from them because they confused me, but now the time has come that I put them to good use. I have been trying to understand how they work and I am just not getting it.
What I am trying to do is populate results based on a string compare function, once I find some match to an 'item name', I would like the first slot to contain the 'item name', then I would like to increment the priority slot by 1.
So when when I'm all done populating my array, it is going to have a variety of different company names, each with their respective priority...
I am having trouble understanding how to declare and manipulate the following array:
$matches = array(
'name'=>array('somename'),
'priority'=>array($priority_level++)
);
So, in what you have, your variable $matches will point to a keyed array, the 'name' element of that array will be an indexed array with 1 entry 'somename', there will be a 'priority' entry with a value which is an indexed array with one entry = $priority_level.
I think, instead what you probably want is something like:
$matches[] = array(name => 'somename', $priority => $priority_level++);
That way, $matches is an indexed array, where each index holds a keyed array, so you could address them as:
$matches[0]['name'] and $matches[0]['priority'], which is more logical for most people.
Multi-dimensional arrays are easy. All they are is an array, where the elements are other arrays.
So, you could have 2 separate arrays:
$name = array('somename');
$priority = array(1);
Or you can have an array that has these 2 arrays as elements:
$matches = array(
'name' => array('somename'),
'priority' => array(1)
);
So, using $matches['name'] would be the same as using $name, they are both arrays, just stored differently.
echo $name[0]; //'somename';
echo $matches['name'][0]; //'somename';
So, to add another name to the $matches array, you can do this:
$matches['name'][] = 'Another Name';
$matches['priority'][] = 2;
print_r($matches); would output:
Array
(
[name] => Array
(
[0] => somename
[1] => Another Name
)
[priority] => Array
(
[0] => 1
[1] => 2
)
)
In this case, could this be also a solution with a single dimensional array?
$matches = array(
'company_1' => 0,
'company_2' => 0,
);
if (isset($matches['company_1'])) {
++$matches['company_1'];
} else {
$matches['company_1'] = 1;
}
It looks up whether the name is already in the list. If not, it sets an array_key for this value. If it finds an already existing value, it just raises the "priority".
In my opinion, an easier structure to work with would be something more like this one:
$matches = array(
array( 'name' => 'somename', 'priority' => $priority_level_for_this_match ),
array( 'name' => 'someothername', 'priority' => $priority_level_for_that_match )
)
To fill this array, start by making an empty one:
$matches = array();
Then, find all of your matches.
$match = array( 'name' => 'somename', 'priority' => $some_priority );
To add that array to your matches, just slap it on the end:
$matches[] = $match;
Once it's filled, you can easily iterate over it:
foreach($matches as $k => $v) {
// The value in this case is also an array, and can be indexed as such
echo( $v['name'] . ': ' . $v['priority'] . '<br>' );
}
You can also sort the matched arrays according to the priority:
function cmp($a, $b) {
if($a['priority'] == $b['priority'])
return 0;
return ($a['priority'] < $b['priority']) ? -1 : 1;
}
usort($matches, 'cmp');
(Sourced from this answer)
$matches['name'][0] --> 'somename'
$matches['priority'][0] ---> the incremented $priority_level value
Like David said in the comments on the question, it sounds like you're not using the right tool for the job. Try:
$priorities = array();
foreach($companies as $company) {
if (!isset($priorities[$company])) { $priorities[$company] = 0; }
$priorities[$company]++;
}
Then you can access the priorities by checking $priorities['SomeCompanyName'];.

php multidimensional array get first date value

I need help once again. I have an array and I need to extract earliest day weight value.
EDIT - EDIT - EDIT
array (
3 =>
array (
'id' => '20110211',
'Date' => '2011-02-11',
'Weight' => '195',
),
4 =>
array (
'id' => '20110213',
'Date' => '2011-02-13',
'Weight' => '160',
),
6 =>
array (
'id' => '20110310',
'Date' => '2011-03-10',
'Weight' => '200',
),
12 =>
array (
'id' => '20110301',
'Date' => '2011-03-01',
'Weight' => '55',
),
21 =>
array (
'id' => '20110215',
'Date' => '2011-02-15',
'Weight' => '120',
),
25 =>
array (
'id' => '20110322',
'Date' => '2011-03-22',
'Weight' => '250',
),
)
I've edited this and this code works:
function sortByDate ($arr1, $arr2)
{
return strcmp($arr1['Date'], $arr2['Date']);
}
// $arr is your array
usort($weight_tracker, 'sortByDate');
$earliest = $weight_tracker[0]['Weight'];
echo $earliest;
But since I have a form on this page which updates the array when array is updated - I got message Fatal error: Cannot use string offset as an array in
EDIT -> I've re-declared this as string, hence the ERROR ! be careful when using global and includes as everything can become a mess ! PHP is forgiving, but that "forgiveness" can cost a lot of time later on... :)
Thanks,
Peter
You could sort the array with a custom callback using usort() and then take the first element.
// $arr is your array
usort($arr, 'sortByDate');
$earliest = $arr[0];
function sortByDate ($arr1, $arr2)
{
return strcmp($arr1['Date'], $arr2['Date']);
}
This is one way of doing it:
function filter_min($a, $b) {
return ($a['Date'] < $b['date']) ? $a : $b;
}
$result = array_reduce($array, 'filter_min');
echo $result['Weight'];
Another way is to simply iterate over the array and find the smallest date.
$smallest = null; // array index of entry with smallest weight.
foreach ($array as $idx => $data) {
if (($data['Weight'] < $array[$smallest]['Weight']) || (is_null($smallest))) {
$smallest = $idx; // found a small weight, so save the index
}
}
echo $array[$smallest]['Date'];
I noticed that the dates are in reverse order with the latest date being pulled in first, and the earliest date last. Will it always be like that? If so, you can do this:
$index = count($array) - 1;
$earliestdate = $array[$index]['Date'];
You could also use array_reverse() to invert the array and make the first element the formerly last element.
If this is being pulled in from MySQL you could also alter the query to ORDER BY 'Date' DESC (or is it ASC that would get you what you want, can't remember)

Categories