Optimizing algorithm to avoid exaggerated script execution time - php

Yesterday I ask here Append array values to another array by keys comparison and I have tried a few things without success. I have two arrays $result and $result1. count($result) is 204640 and count($result1) is 129849 so they are huge. The arrays is the result of a PDO statement execution as shown in code below. This is an example for $result array:
'00180000015oGSWAA2' =>
array (
'accountId' => '00180000015oGSWAA2',
'npi' => '1053576223',
'firstname' => 'Jack',
'lastname' => 'Cheng',
'title' => '',
'accountLastModifiedDate' => '2014-09-09 17:37:15',
'suffix' => '',
'fax' => '',
'address1' => '853 N CHURCH ST',
'city' => 'SPARTANBURG',
'stateLicensedId' => '31191',
'state' => 'SC',
'phone' => '',
'zip' => '29303',
'address2' => '',
'addressLastModifiedDate' => '2014-09-04 08:44:17',
),
'00180000015oGeXAAU' =>
array (
'accountId' => '00180000015oGeXAAU',
'npi' => '1629067301',
'firstname' => 'Fred',
'lastname' => 'Thaler',
'title' => '',
'accountLastModifiedDate' => '2014-09-09 17:36:41',
'suffix' => '',
'fax' => '',
'address1' => '1 PEARL ST',
'city' => 'BROCKTON',
'stateLicensedId' => '58249',
'state' => 'MA',
'phone' => '',
'zip' => '2301',
'address2' => '',
'addressLastModifiedDate' => '2014-09-04 04:25:44',
)
And this is an example for $result1 array:
'001S000000nBvryIAC' =>
array (
'tid' => '04T800000008zySEAQ',
'acsLastModifiedDate' => '2015-01-06 17:19:48',
),
'00180000015oGeXAAU' =>
array (
'tid' => '04T800000008zzgEAA',
'acsLastModifiedDate' => '2015-01-07 04:06:40',
),
'001S000000nYWcYIAW' =>
array (
'tid' => '04T800000008zySEAQ',
'acsLastModifiedDate' => '2015-02-25 15:45:01',
),
As you can see $result[1] and $result1[1] shares the same keys, right? Ok, then what I need is to push the content of $result1[1] on the end of $result[1] and getting something like:
'00180000015oGeXAAU' =>
array (
'accountId' => '00180000015oGeXAAU',
'npi' => '1629067301',
'firstname' => 'Fred',
'lastname' => 'Thaler',
'title' => '',
'accountLastModifiedDate' => '2014-09-09 17:36:41',
'suffix' => '',
'fax' => '',
'address1' => '1 PEARL ST',
'city' => 'BROCKTON',
'stateLicensedId' => '58249',
'state' => 'MA',
'phone' => '',
'zip' => '2301',
'address2' => '',
'addressLastModifiedDate' => '2014-09-04 04:25:44',
'tid' => '04T800000008zzgEAA',
'acsLastModifiedDate' => '2015-01-07 04:06:40',
)
Meaning when keys are equal in both arrays then merge or append values from the second one into the first one. Right now my best approach is the following:
// PDO statement
$result = $stmt->fetchAll();
// PDO statement
$result1 = $stmt->fetchAll();
foreach ($result as $key => $row) {
foreach ($result1 as $key1 => $row1) {
if ($key === $key1) {
array_push($row, array_shift($row1));
}
}
}
var_dump($row);
But it takes an eternity due to arrays length, so any advice in how to speedup this?
UPDATE: almost a solution
Based on #decese solution I have rewrite this a bit so my apologies in first and take a look to this. Now I called arrays as $oneArr and $twoArr and also I write a small output examples (don't kill me just yet):
// var_export($oneArr)
'00180000015oGSWAA2' =>
array (
'target_id' => '00180000015oGSWAA2',
'firstname' => 'Jack',
),
'00180000015oGeXAAU' =>
array (
'target_id' => '00180000015oGeXAAU',
'firstname' => 'Fred',
)
// var_export($twoArr)
'001S000000nBvryIAC' =>
array (
'tid' => '04T800000008zySEAQ',
'acsLastModifiedDate' => '2015-01-06 17:19:48',
),
'00180000015oGeXAAU' =>
array (
'tid' => '04T800000008zzgEAA',
'acsLastModifiedDate' => '2015-01-07 04:06:40',
)
This is the ouput I want to achieve, meaning when keys are equal in both arrays then merge or append values from the second one into the first one:
'00180000015oGeXAAU' =>
array (
'target_id' => '00180000015oGeXAAU',
'firstname' => 'Fred',
'tid' => '04T800000008zzgEAA',
'acsLastModifiedDate' => '2015-01-07 04:06:40',
)
I have this done, again based on first answer which I thanks a lot for clear a bit things to me, with the following code:
// Fetch results from first SQL query
$result = $stmt->fetchAll();
// Fetch only two keys from the entire and put them on $oneArr
foreach ($result as $row) {
$oneArr[$row['target_id']] = [
'target_id' => $row['target_id'],
'firstname' => $row['firstname']
];
}
// Fetch results from second SQL query
// yes, var names are the same not matter, I will only use one time
$result = $stmt->fetchAll();
// Fetch only two keys from the entire and put them on $twoArr
foreach ($result as $row) {
$twoArr[$row['target_id']] = [
'tid' => $row['tid'],
'acslmd' => $row['acslmd']
];
}
$i = 0;
foreach ($oneArr as $keyOneArr => $valueOneArr) {
if (array_key_exists($keyOneArr, $twoArr)) {
array_push($oneArr[$keyOneArr], $twoArr[$keyOneArr]);
$i++;
}
}
var_export($oneArr);
But result is not the same as I want since I got this:
'00180000015oGeXAAU' =>
array (
'target_id' => '00180000015oGeXAAU',
'firstname' => 'Fred',
0 =>
array (
'tid' => '04T800000008zzgEAA',
'acslmd' => '2015-01-07 04:06:40',
),
),
How I can avoid the extra array on the result array?
PS: Times looks good now: Call time was 00:01:34

Looks like your arrays are actually indexed by the ids, so do this:
foreach ($result1 as $key => $value) {
if (isset($result[$key])) {
$result[$key] += $value;
}
}
Of course, doing all this work in the database using a JOIN would make the most sense to begin with, if possible.

You could use array_merge_recursive
See my working example: http://3v4l.org/hQERW
Note: in the provided data, only 00180000015oGeXAAU is common to both arrays, so it only merges the values for that key
Basically, just call
$result = array_merge_recursive($result, $result1);
PHP.net says:
array_merge_recursive() merges the elements of one or more arrays together so that the values of one are appended to the end of the previous one. It returns the resulting array.
If the input arrays have the same string keys, then the values for these keys are merged together into an array, and this is done recursively, so that if one of the values is an array itself, the function will merge it with a corresponding entry in another array too. If, however, the arrays have the same numeric key, the later value will not overwrite the original value, but will be appended.

Related

PHP search in multidimensional array. without knowing key

Array:
$userdb = array(
array(
'uid' => '100',
'name' => 'Sandra Shush',
'pic_square' => 'abc.jpg'
),
array(
'uid' => '5465',
'name' => 'Stefanie Mcmohn',
'pic_square' => 'Michael.jpg'
),
array(
'uid' => '40489',
'name' => 'Michael',
'pic_square' => 'xyz.jpg'
)
);
Search value:
Michael
Expected output:
$userdb = array(
array(
'uid' => '5465',
'name' => 'Stefanie Mcmohn',
'pic_square' => 'Michael.jpg'
),
array(
'uid' => '40489',
'name' => 'Michael',
'pic_square' => 'xyz.jpg'
)
);
As you can check what I want can anyone please tell me How can I achieve this result by using PHP. I don't care whether my search term is in 'uid', 'name', or 'pic_square' key but I want to get array whenever my search term result is matching. Is there any inbuilt function which can achieve this result?
Another way is to use array_filter() to remove elements that not match to your search.
$userdb = array(
array('uid' => '100', 'name' => 'Sandra Shush', 'pic_square' => 'abc.jpg'),
array('uid' => '5465', 'name' => 'Stefanie Mcmohn', 'pic_square' => 'Michael.jpg'),
array('uid' => '40489', 'name' => 'Michael', 'pic_square' => 'xyz.jpg')
);
$search = 'Michael';
$out = array_filter($userdb, function($item) use($search) {
return $item['name'] == $search || strpos($item['pic_square'], $search) !== false;
});
print_r($out);
Output:
Array (
[1] => Array (
[uid] => 5465
[name] => Stefanie Mcmohn
[pic_square] => Michael.jpg
)
[2] => Array (
[uid] => 40489
[name] => Michael
[pic_square] => xyz.jpg
)
)
Very simple solution is to apply simple foreach() with strpos()
1.iterate over the array using foreach()
2.Check that search value exist in any one of three id, name,pic_square or not? if yes then add that whole sub-array to a new array.
3.This new array is your desired result.
$search_value = 'Michael';
$final_array = [];
foreach($userdb as $userd){
if(strpos($userd['uid'],$search_value)!== false || strpos($userd['name'],$search_value)!== false || strpos($userd['pic_square'],$search_value)!== false){
$final_array[] = $userd;
}
}
print_r($final_array);
Output:- https://eval.in/997896
You can use preg_grep to match the search in a loose comparison.
$search = "Michael";
Foreach($userdb as $key => $arr){
If(preg_grep("/" . $search ."/", $arr)){
$res[] = $userdb[$key];
}
}
Var_dump($res);
I loop through the array and if a match is made with preg_grep it's added to the result array.
https://3v4l.org/BPJBC
Preg_grep will search the full array and not only items hardcoded.
This can be both a good and a bad thing obviously.
As an example if your DB expands with 'alias' key preg_grep will search that to without needing to change the code.
See here for an example:
https://3v4l.org/lOao3
This should do the trick.
foreach($userdb as $i=>$r){
foreach($r as $j=>$v){
#if($v matches the querystring)
#return $r / append $r to a match list
}
}
But NOTE THAT since there are no indexing and all, this will run in O(N^2) which is the worst case...
EDIT
After some research, came up with the following solution.
$userdb = array(
array(
'uid' => '100',
'name' => 'Sandra Shush',
'pic_square' => 'abc.jpg'
),
array(
'uid' => '5465',
'name' => 'Stefanie Mcmohn',
'pic_square' => 'Michael.jpg'
),
array(
'uid' => '40489',
'name' => 'Michael',
'pic_square' => 'xyz.jpg'
)
);
$search = "Michael";
$out = array_filter($userdb, function($item) use($search) {
foreach($item as $k=>$v){
if(preg_match("/$search/", $v) == 1){
return true;
}
}
});
echo json_encode($out);
The solution employs array_filter function (Syscall's answer is acknowledged hereby, that was the pointer for me to research on array_filter further) to reduce the array using a filter, where the filter being a preg_match executed on each attribute of the associative array elements. If a match found, it returns true, which will add the matching element into $out.

Access nested objects and arrays

I am working with the Mailgun API. I have a variable with some objects and arrays inside that looks like this:
$result =
stdClass::__set_state(array(
'http_response_body' =>
stdClass::__set_state(array(
'items' =>
array (
0 =>
stdClass::__set_state(array(
'geolocation' =>
stdClass::__set_state(array(
'country' => 'UK',
'region' => '72',
'city' => 'London',
)),
'tags' =>
array (
),
'mailing-list' =>
stdClass::__set_state(array(
'list-id' => 'email#address.com',
'address' => 'email#address.com',
'sid' => '123456',
)),
'ip' => '123.456.789',
'log-level' => 'info',
'id' => 'ABCA',
'campaigns' =>
array (
0 =>
stdClass::__set_state(array(
'name' => 'TEST',
'id' => 'test123',
)),
),
)),
),
'http_response_code' => 200,
));
?>
I need to access every single information inside (e.g. the IP, the country, the region), . I tried multiple solutions, including using a foreach.
foreach ($result as $row) {
echo $row->items->ip;
}
However I keep getting
Catchable fatal error: Object of class stdClass could not be converted to string
I know I am not accessing the data the right way. Could you advice?
I think that you mean to be looping over $result->http_response_body->items. What you are actually doing is looping over $result->items which doesn't seem to exist.
foreach($result->http_response_body->items as $item) {
echo $item->ip;
}

Sum of Dynamic Multi-Dimension array in PHP

I searched for few in-built methods and tried few logics from here and there but none can make me satisfied. I have created my own logic but is limited to a certain dimension of the array. I have managed to solve my problem for the project but as I said I am not satisfied.
Consider the array below:
$data = array
(
'0' => array
(
'0' => array
(
'make_ready' => '',
'ready_time' => '0.55',
'rate' => '46',
'no_of_run' => '',
'fixed_cost' => '',
'variable_cost' => '25.3',
'0' => array(
'kg' => 2.66,
'rate' => 11.4,
'fixed_cost' => '',
'variable_cost' => 30.32,
'0' => array(
'kg' => 2.66,
'rate' => 11.4,
'fixed_cost' => '',
'variable_cost' => 30.32,
),
),
),
),
'1' => array
(
'0' => array
(
'make_ready' => '1',
'ready_time' => '1.16',
'rate' => '36.47',
'no_of_run' => '',
'fixed_cost' => '36.47',
'variable_cost' => '42.31',
),
),
'2' => array
(
'make_ready' => '2',
'ready_time' => '0.29',
'rate' => '360',
'no_of_run' => '',
'fixed_cost' => '720',
'variable_cost' => '104.4',
),
'size' => '1000 X 1200 X 1190',
'up' => '3 X 4 / 17',
'unit' => '4',
'rate' => 16.32,
'fixed_cost' => '',
'variable_cost' => 65.28,
);
Problem:
I want to sum up all the values of array element where key is 'variable_cost', no matter how deep (in terms of dimension) is the array. Basically like a loop that can scan all the elements and do 'sum' if key matches.
I would like someone to help/suggest any logic where the calculations can be done till nth dimension.
For example like the movie Inception, in which they can go
dream->with in a dream->to nth number of dream.
and come out with the sum of 'variable_cost'. Hope you guys can understand the question here.
Thank you.
You will need to use a recursive function to sum all the variable_cost values:
function sum_variable_cost( $data, $total=0) {
foreach ($data as $key => $item) {
if (is_array($item)) {
$total = sum_variable_cost($item, $total);
} else if ($key=='variable_cost') {
//echo $key . " " . $item . "\n";
$total += $item;
}
}
return $total;
}
echo sum_variable_cost( $data );
Basically it loops through the array and sees if there are other arrays and only adds values that contain the key variable_cost.
If you uncomment the echo line it will show this output:
variable_cost 25.3
variable_cost 30.32
variable_cost 30.32
variable_cost 42.31
variable_cost 104.4
variable_cost 65.28
297.93

Condensing, restructuring and adding subarrays

I've been scratching my head and failing miserably at coming up with a solution to my array structuring issue. I'm not sure exactly what part would be better to try and fix, the data being returned from SQL or the PHP array after the fact.
My SQL data is returned like this:
$i = 0;
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ) ) {
$colData[$i] = array(
'name' => $row['FULLNAME'],
'invoice' => $row['CUST_InvoiceNumber_020911544'],
array(
'service' => $row['CUST_Service_052400634'],
'date' => date_normalizer($row['CUST_ServiceDate_064616924']),
'service_amount' => $row['CUST_ServiceAmount_054855553'],
),
'do_all_for' => $row['CUST_DoAllFor_021206685'],
'memo' => $row['CUST_Memo_021614200'],
'paymenttype' => $row['CUST_PAYMENTTYPE_123838203'],
'deposit' => $row['CUST_DEPOSIT_124139703'],
'datepaid' => date_normalizer($row['CUST_DATEPAID_124941578']),
);
$i++;
}
And the resultant array has this structure:
array (
0 =>
array (
'name' => 'ABRAHAM PRETORIS',
'invoice' => '63954',
0 =>
array (
'service' => 'Tree Work',
'date' => '2015-01-22',
'service_amount' => '1305.00',
),
'do_all_for' => '4924.68',
'memo' => 'CHECK #947 $2400',
'paymenttype' => 'VISA',
'deposit' => '4429.48',
'datepaid' => '2015-02-09',
),
1 =>
array (
'name' => 'ABRAHAM PRETORIS',
'invoice' => '63954',
0 =>
array (
'service' => 'DRF',
'date' => '2015-01-22',
'service_amount' => '740.00',
),
'do_all_for' => '4924.68',
'memo' => 'CHECK #947 $2400',
'paymenttype' => 'VISA',
'deposit' => '4429.48',
'datepaid' => '2015-02-09',
),
2 =>
array (
'name' => 'ABRAHAM PRETORIS',
'invoice' => '63954',
0 =>
array (
'service' => 'Stumps',
'date' => '2015-01-26',
'service_amount' => '360.00',
),
'do_all_for' => '4924.68',
'memo' => 'CHECK #947 $2400',
'paymenttype' => 'VISA',
'deposit' => '4429.48',
'datepaid' => '2015-02-09',
),
Notice that I'm getting a new subarray for the same person because the sub-subarray (service, date & service_amount) has multiple values.
What I'm trying to accomplish is condensing the array so that I only have one array for "ABRAHAM PRETORIS" etc, but all of the different services listed as a sub array. I would like it to look like this:
array (
0 =>
array (
'name' => 'ABRAHAM PRETORIS',
'invoice' => '63954',
0 =>
array (
'service' => 'Tree Work',
'date' => '2015-01-22',
'service_amount' => '1305.00',
),
1 =>
array (
'service' => 'DRF',
'date' => '2015-01-22',
'service_amount' => '740.00',
),
2 =>
array (
'service' => 'STUMPS',
'date' => '2015-01-26',
'service_amount' => '360.00',
),
'do_all_for' => '4924.68',
'memo' => 'CHECK #947 $2400',
'paymenttype' => 'VISA',
'deposit' => '4429.48',
'datepaid' => '2015-02-09',
),
I've looked at tons of examples of nested foreach statements and php array functions but I just can't wrap my head around how to loop through and add the additional services to the array then proceed when it's a row with a different name and/or invoice number.
Thanks in advance for the help!!
First, make sure your SQL query has an order by name, invoice. That will ensure all the records you want to group are sequential.
Then you have to create a loop with some additional inner logic:
// Creates an array to hold the final array.
$result = array();
// This var will keep track of name changes.
$current_name = '';
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ) )
{
// Let's check if the name changed. This will be true for the first
// time the loop runs.
if($current_name != $row['FULLNAME'])
{
// If we are beginning, the if below will not run. But in subsequent
// records, it will add the acumulated array to the main result.
if($current_name != '') $result[] = $temp;
// The temp array will be populated with all data that DOES NOT change
// for the current name.
$temp = array('name' => $row['FULLNAME'],
'invoice' => $row['CUST_InvoiceNumber_020911544'],
'do_all_for' => $row['CUST_DoAllFor_021206685'],
'memo' => $row['CUST_Memo_021614200'],
'paymenttype' => $row['CUST_PAYMENTTYPE_123838203'],
'deposit' => $row['CUST_DEPOSIT_124139703'],
'datepaid' => date_normalizer($row['CUST_DATEPAID_124941578']),
);
// Update the current name.
$current_name = $row['FULLNAME'];
}
// The part that runs only on name changes has finished. From now on, we
// will take care of data which will be accumulated
// in a sub-array (until name changes and the block above resets it).
$temp['sub-array'][] =
array('service' => $row['CUST_Service_052400634'],
'date' => date_normalizer($row['CUST_ServiceDate_064616924']),
'service_amount' => $row['CUST_ServiceAmount_054855553']);
}
// After the loop, the last temp array needs to be added too.
$result[] = $temp;
This is the general concept: you will create a temporary array to hold the current name, inside which you will acummulate other data. Once the name changes, the acummulated data will be dumped to the main result, the temp array is reset, and a new acummulation begins.
I can't test the code right now, so it probably needs some fixes, but this approach works really well, and my point here is to show you the concept, so you can adapt it to your specific needs.

CakePHP use Hash to prepare Model array for saving

I'm wondering how one might use CakePHP Hash functions to achieve the following:
This is the array before (Plugin stored in Session):
$Data = array(
'add' => array(
'Certificate' => array(
'name' => '',
'entity_id' => '1',
'state_id' => '18',
'domicile' => '',
'formation_date' => array(
'month' => '',
'day' => '',
'year' => ''
)
)
),
'add2' => array(
'Certificate' => array(
'filing_fees' => '82'
)
)
)
This is the array after. I am reformatting it to call Certificate->save(). I removed the top level and merged the Certificate model.
$Data = array(
'Certificate' => array(
'name' => '',
'entity_id' => '1',
'state_id' => '18',
'domicile' => '',
'formation_date' => array(
'month' => '',
'day' => '',
'year' => ''
),
'filing_fees' => '82'
)
)
This is my current code:
$processed = array();
foreach ($Data as $k => $array1) {
foreach ($array1 as $array1k => $array2) {
if (!isset($processed[$array1k])) {
$processed[$array1k] = array();
}
foreach ($array2 as $a2k => $a2v) {
$processed[$array1k][$a2k] = $a2v;
}
}
}
What I have works, but it's not very "Cake-ish" and will certainly cause problems when I need to prepare data for saving more complex model relationships (e.g. HABTM saveall).
I tried Hash::combine, Hash::merge, array_merge_recursive(), and combinations of these before posting. I understand the PHP logic, but I am lost to find the CakePHP functions with similar logic.
The first step is to merge the Model, then the second step is to remove the key ('add, 'add2'). The closest I got was Hash::combine, but there was no way to reference a $keyPath {s} of the Model, skipping over the first level ('add', 'add2') of the array.
If you could point me in the direction of which Hash or other CakePHP functions are recommended, I'd be happy to take the next step.

Categories