Before I add clarification, here is some pseudo data. The array I need to iterate is like this:
$ipBodies = array(
'1.2.3.4' => array(
array('id' => 1, 'body' => 'asdfasdfasdf_X'),
array('id' => 2, 'body' => 'asdfasdfasdf_Y'),
array('id' => 3, 'body' => '123456789_X'),
array('id' => 4, 'body' => '123456789_Y'),
),
'5.6.7.8' => array(
array('id' => 13, 'body' => 'foobarbaz_X'),
array('id' => 14, 'body' => 'foobarbaz_Y'),
array('id' => 15, 'body' => 'adsflkjlsdfjlkjlkasdfj'),
array('id' => 16, 'body' => 'foobarbaz_Z'),
),
);
So from this sample data, you can see there are two sets of unique 'fuzzy duplicates' in the 1.2.3.4 array, and only 1 set of 'fuzzy duplicates' in the 5.6.7.8 array.
In the real data, everything is scaled up. The main array will have hundreds of ip addresses, and those arrays could have hundreds of members. Also the body section is larger in the real data.
I've considered that I need to run through each ip address array and create a new array of every combination to a new array, say $pairs, then run similar_text (seems to work well for this) on those to find duplicates, but creating these sets of pairs will be expensive I believe. I think the $pairs array count would end up being the factorial of the count of the array, which could become enormous as the array size increases.
I'm thinking I'd like to end up with an array $dupes that (based on the sample data above) should look like this:
$dupes = array(
'1.2.3.4' => array(
array('1', '2'),
array('3', '4'),
),
'5.6.7.8' => array(
array('13', '14', '16'),
),
);
I really just need some help and advice here so I can start solving the problem. God I hope my explanation made sense. If it didn't, let me know and I'll clarify.
If possible, I recommend using levenshtein instead of similar_text because it's a faster algorithm.
The complexity of the algorithm is O(m*n), where n and m are the
length of str1 and str2 (rather good when compared to similar_text(),
which is O(max(n,m)**3), but still expensive).
The code below uses an associative array to put each element into buckets where the ip['body'] has levensthein distance of < 2 (which means matches within the same bucket will have at most 1 different character, change as needed). Once all elements have been placed into their respective buckets every bucket with only 1 element is discarded.
$ipBodies = array(
'1.2.3.4' => array(
array('id' => 1, 'body' => 'asdfasdfasdf_X'),
array('id' => 2, 'body' => 'asdfasdfasdf_Y'),
array('id' => 3, 'body' => '123456789_X'),
array('id' => 4, 'body' => '123456789_Y'),
),
'5.6.7.8' => array(
array('id' => 13, 'body' => 'foobarbaz_X'),
array('id' => 14, 'body' => 'foobarbaz_Y'),
array('id' => 15, 'body' => 'adsflkjlsdfjlkjlkasdfj'),
array('id' => 16, 'body' => 'foobarbaz_Z'),
),
);
$counts = [];
foreach($ipBodies as $groupName => $group) {
$counts[$groupName] = [];
foreach($group as $key => $ip) {
foreach($counts[$groupName] as $countGroup => $groupCount) {
if(levenshtein($ip['body'],$countGroup) < 2) {
$counts[$groupName][$countGroup][] = $ip['id'];
continue 2;
}
}
$counts[$groupName][$ip['body']] = [$ip['id']];
}
}
//remove elements that appear just once
foreach($counts as $groupName => &$groupCounts) {
foreach($groupCounts as $k => &$v) {
if(count($v) < 2) {
unset($counts[$groupName][$k]);
}
}
$counts[$groupName] = array_values($groupCounts);
}
print_r($counts);
Output
Array
(
[1.2.3.4] => Array
(
[0] => Array
(
[0] => 1
[1] => 2
)
[1] => Array
(
[0] => 3
[1] => 4
)
)
[5.6.7.8] => Array
(
[0] => Array
(
[0] => 13
[1] => 14
[2] => 16
)
)
)
Related
I have an array
$info = array(
[0] => array(
'id' => 1,
'uid' => '677674e21aed487fd7180da4a7619a9d'
),
[1] => array(
'id' => 1,
'uid' => 'd3c98a10fe4e42fb1fe868008c0f4cc1'
),
[2] => array(
'id' => 1,
'uid' => 'd3c98a10fe4e42fb1fe868008c0f4cc1'
),
[3] => array(
'id' => 1,
'uid' => '658284e5395a29bf34d21f30a854e965'
),
[4] => array(
'id' => 1,
'uid' => '01f33ae45a463e0c1de4ad989b3ccad5'
),
[5] => array(
'id' => 1,
'uid' => '677674e21aed487fd7180da4a7619a9d'
)
)
As you can see, uid of 0th index and 5th index are same. Similarly, uid of 2nd index and 3rd index are same.
I want a PHP script by which I can randomly create one hexadecimal color code for duplicate uids. Say something like this.
$info = array(
[0] => array(
'id' => 1,
'uid' => '677674e21aed487fd7180da4a7619a9d',
'col' => 'black'
),
[1] => array(
'id' => 1,
'uid' => 'd3c98a10fe4e42fb1fe868008c0f4cc1',
'col' => 'green'
),
[2] => array(
'id' => 1,
'uid' => 'd3c98a10fe4e42fb1fe868008c0f4cc1',
'col' => 'green'
),
[3] => array(
'id' => 1,
'uid' => '658284e5395a29bf34d21f30a854e965'
),
[4] => array(
'id' => 1,
'uid' => '01f33ae45a463e0c1de4ad989b3ccad5'
),
[5] => array(
'id' => 1,
'uid' => '677674e21aed487fd7180da4a7619a9d',
'col' => 'black'
)
)
How can I do this with the most minimum execution time?
There might be various ways for doing this workout, but due to lack of proper response, I came up with this probable lengthier code. I am posting the answer here for people who might need this.
$uidArray = array(); // creating a blank array to feed each uid
$uidDuplicateArray = array(); // creating a blank array as container to hold duplicate uid(s) only
foreach($all_data as $key => $ad)
{
// iterate through each item of the list
/.................
.................. //
$uidArray[] = $ad['uid'];
}
foreach(array_count_values($uidArray) as $val => $c)
{
if($c > 1)
{
// if count value is more than 1, then its duplicate
// set the uid duplicate array with key as uid and unique color code as value
$uidDuplicateArray[$val] = sprintf('#%06X', mt_rand(0, 0xFFFFFF));
}
}
foreach($all_data as $keyAgain => $adg)
{
// iterating through each item of original data
if(isset($uidDuplicateArray[$adg['uid']]))
{
// if the uid is key of the duplicate array, feed the value to original array in a new key.
$all_data[$keyAgain]['color'] = $uidDuplicateArray[$adg['uid']];
}
}
Each comment associated with each LOC is self explanatory.
The reason I wanted this, is to mark the duplicates in UI like this:-
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
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.
I have this Array:
$mergedItems = array(
0 => array(
'id_item' => 'AZ-110'
'amount' => 12
),
1 => array(
'id_item' => 'BZ-110',
'amount' => 13
),
2 => array(
'id_item' => 'BZ-210',
'amount' => 28
),
3 => array(
'id_item' => 'CZ-291',
'amount' => 11
)
);
AND this Array:
$items = array(
0 => array(
'number' => 'AZ-110'
),
1 => array(
'number' => 'BZ-110'
),
2 => array(
'number' => 'CZ-291'
),
3 => array(
'number' => 'BZ-210'
)
);
Now what i want is to order the first array by the id_item Value to match the same order than the 2nd one by its values.
The resulting array has to include all values of the 2nd array AND the belonging amount-value of the first array. The Keys must not be kept!
I can't use array_merge since the 2nd Array has a dynamic amount of more items, so i only want all items from the second Array that are set in the first one.
Does anyone get what i mean? I am searching for a quick and non-dirty way to get this result as expected.
/Edit:
Expected Array:
$detailedItems = array(
0 => array(
'number' => 'AZ-110',
'amount' => 12
),
1 => array(
'number' => 'BZ-110',
'amount' => 13
),
2 => array(
'number' => 'CZ-291',
'amount' => 11
),
3 => array(
'number' => 'BZ-210',
'amount' => 28
)
);
A PHP 5.5 solution:
$itemMap = array_flip(array_column($mergedItems, 'id_item'));
$result = array_map(
function($i) use($itemMap, $mergedItems) {
return $mergedItems[$itemMap[$i['number']]];
},
$items);
print_r($result);
For 5.3 <= PHP < 5.5 you can simply substitute array_map for array_column:
$itemMap = array_flip(array_map(
function($i) { return $i['id_item']; },
$mergedItems));
How it works
The idea is to create a map of item numbers to indexes inside $mergedItems, ie.
[
'AZ-100' => 0,
'BZ-110' => 1,
'BZ-210' => 2,
// etc
]
With this information at hand it's very easy to iterate over $items (so that the result will be ordered based on that array) and pick the appropriate element from $mergedItems to append to the result each time.
$temp = $items;
foreach($temp as &$val)
{
foreach($mergedItems as $item)
{
if($item['id_item'] == $val['number'])
{
$val['amount'] = $item['amount'];
break;
}
}
}
print_r($temp);
There isn't really a "non-dirty" (meaning single line) way to do this as far as I know, but this function should work:
$out = array();
foreach ($mergedItems as $key => $value) {
if (array_key_exists($key, $detailedItems)) { // Make sure it exists to prevent errors
$out[$key] = $detailedItems[$key] + array('amount' => $value['amount']);
} else {
$out[$key] = $value['amount'];
}
}
print_r($out);
You can try following codes:
foreach ($mergedItems as $item) {
$merged[$item['id_item']] = array('amount' => $item['amount']);
}
foreach ($items as $item)
{
$detailedItems[] = array_merge($item, $merged[$item['number']]);
}
Output
var_dump($detailedItems);
array (size=4)
0 =>
array (size=2)
'number' => string 'AZ-110' (length=6)
'amount' => int 12
1 =>
array (size=2)
'number' => string 'BZ-110' (length=6)
'amount' => int 13
2 =>
array (size=2)
'number' => string 'CZ-291' (length=6)
'amount' => int 11
3 =>
array (size=2)
'number' => string 'BZ-210' (length=6)
'amount' => int 28
I have an array:
$initialarray = array(
0 = array(
'unit' => 1,
'class' => 1,
'value' => 'string1'
),
1 = array(
'unit' => 1,
'class' => 2,
'value' => 'string2'
),
2 = array(
'unit' => 1,
'class' => 2,
'value' => 'string3'
),
3 = array(
'unit' => 2,
'class' => 1,
'value' => 'string4'
)
4 = array(
'unit' => 2,
'class' => 2,
'value' => 'string5'
)
);
What would be the best way to structure it (to group the resulting sub-arrays) depending first on the 'unit' field's values, and then depending on the 'class' field's values, like so:
$resultarray = array(
// array of all the sub-arrays of 'unit' = 1
$unit[1] = array(
// array of all the sub-arrays of 'unit' = 1 and 'class' = 1
$class[1] = array(
0 = array(
'unit' => 1,
'class' => 1,
'value' => 'string1'
)
)
// array of all the sub-arrays of 'unit' = 1 and 'class' = 2
$class[2] = array(
0 = array(
'unit' => 1,
'class' => 2,
'value' => 'string2'
),
1 = array(
'unit' => 1,
'class' => 2,
'value' => 'string3'
)
)
)
// array of all the sub-arrays of 'unit' = 2
$unit[2] = array(
// array of all the sub-arrays of 'unit' = 2 and 'class' = 1
$class[1] = array(
0 = array(
'unit' => 2,
'class' => 1,
'value' => 'string4'
)
)
// array of all the sub-arrays of 'unit' = 2 and 'class' = 2
$class[2] = array(
0 = array(
'unit' => 2,
'class' => 2,
'value' => 'string5'
)
)
)
)
I have asked a similar question here and got a working answer for only one iteration, i.e. for only structuring the array by one of the fields. But I could not make the same solution work for multiple iterations, i.e. for more than one field.
Also, is there a solution to structure a multidimensional array depending on more than two fields?
I think it's not a way of asking the question. It is very simple , you can do this by playing with arrays,keys and etc.... So first you should try hard for the problem. After If you have any problem in the middle of your tries then you can ask that here. I have solved your problem here is the complete code , but next time please do some work and then only post the problem. Never ask for the code.
foreach ($initialarray as $key1=>$val1)
{
foreach ($val1 as $key2=>$val2)
{
if($key2=='unit')
{
$num=$val2;
if($val2!=$num)
$testarr['unit'.$val2]=array();
}
if($key2=='class')
{
$testarr['unit'.$num]['class'.$val2][]=$val1;
}
}
}
print_r($testarr);
I must offer a better way for you and future researchers...
You only need one loop, and you merely need to nominate the result array's key values before using [] to "push" new data into the deepest subarray.
*there is absolutely no need for any condition statements or a second loop.
Code: (Demo)
$initialarray = [
['unit' => 1, 'class' => 1, 'value' => 'string1'],
['unit' => 1, 'class' => 2, 'value' => 'string2'],
['unit' => 1, 'class' => 2, 'value' => 'string3'],
['unit' => 2, 'class' => 1, 'value' => 'string4'],
['unit' => 2, 'class' => 2, 'value' => 'string5']
];
foreach ($initialarray as $row) {
$result[$row['unit']][$row['class']][] = $row;
}
var_export($result);
Output:
array (
1 =>
array (
1 =>
array (
0 =>
array (
'unit' => 1,
'class' => 1,
'value' => 'string1',
),
),
2 =>
array (
0 =>
array (
'unit' => 1,
'class' => 2,
'value' => 'string2',
),
1 =>
array (
'unit' => 1,
'class' => 2,
'value' => 'string3',
),
),
),
2 =>
array (
1 =>
array (
0 =>
array (
'unit' => 2,
'class' => 1,
'value' => 'string4',
),
),
2 =>
array (
0 =>
array (
'unit' => 2,
'class' => 2,
'value' => 'string5',
),
),
),
)
If I may express myself in the following manner: I only see the front-end of your problem and know nothing about its back-end, e.g. "Where does the data come from?", "How is it collected and stored", etc. so my answer might not be a real help but still I'll give my "tuppence".
If you can store all that data in a relational database (in form of table(s)) it would be much more easier and faster(!) to select the needed data from the database instead of rearranging arrays, which will take some more time in comparison.
Just as an example you might then select (and store it into an array) all items which have unit = '1' and / or all items which have class = '2'. That would make life much more easier IMHO, than having all the data in a multidimensional array and then try to sort it / rearrange it. Especially if you do that based on more than one property.