Joining arrays in PHP (like MySQL join) - php

I would like to join two arrays in PHP, the same as I could do with MySQL join left. I have two arrays:
$db_products from database (it has id and sku fields)
$csv_prices from a CSV file (it has sku and price fields)
$db_products: (it has 4000 items)
Array
(
[0] => Array
(
[id] => 1012
[sku] => asd123
)
[N] => Array
(
[id] => ...
[sku] => ...
)
)
$csv_prices: (it has 8000 items)
Array
(
[0] => Array
(
[sku] => asd123
[price] => 15.50
)
[N] => Array
(
[sku] => ...
[price] => ...
)
)
The join is $db_products[$key][sku] = $csv_prices[$key][sku]. To find the matching pair, I am doing loops in loops which result in 4000 * 8000 check for match. It consumes a lot of energy and time, and I want to be more efficient.
I could lower by 10% the used time, by unset()-ing the used $csv_prices[$key], but I want to be more efficient.

If you loop through the $csv_products array once and set the SKU as the array key then you won't have to exponentially loop through that array each time you have a new product to find its match.
Instead, you just loop through the product array and use isset() to see if it exists or not.
This way you'll only need to do 1x count($db_products) and 1x count($csv_prices) repetitions, instead of count($db_products) * count($csv_prices) reps (12 thousand vs 32 million).
The concept could be considered to be similar to indexing in databases - you use the key you want to look up as the array key/index, meaning you don't have to loop through the array every time to find the record you want.
Example:
// Reindex the CSV array to use SKU as the key
$reindexed = array();
foreach ($csv_prices as $values) {
$reindexed[$values['sku']] = $values;
}
unset($csv_prices);
// Join the prices
foreach ($db_products as &$product) {
$sku = $product['sku'];
$product['price'] = isset($reindexed[$sku]) ? $reindexed[$sku]['price'] : '';
}
Of course, you won't see a remarkable difference in performance until you start using large data sets - the change would be noticeable exponentially as the data sets grow.

This is working fine for me
function left_join_arrays($key, $array1, $array2) {
$i=0;
foreach ($array1 as $arr1) {
foreach ($array2 as $arr2) {
if ($arr1[$key]==$arr2[$key]) {
foreach(array_keys($arr2) as $key2){
if ($key != $key2) {
$array1[$i][$key2]=$arr2[$key2];
}
}
continue;
}
}
$i++;
}
return $array1;
}

Related

Query 2 dimensional array using native php array functions

I have converted a CSV to a two dimensional array where the following array structure stores the column and row data
$table['status'] = ['active', 'active', 'inactive'];
$table['plan'] = ['annual', 'weekly', 'weekly '];
$table['spend'] = ['12,000', '19,000', '0' ];
print_r($table);
would appear as follows:
( [status] => Array ( [0] => active [1] => active [2] => inactive )
[plan] => Array ( [0] => annual [1] => weekly [2] => weekly )
[spend] => Array ( [0] => 12,000 [1] => 19,000 [2] => 0 ) )
I want to use native PHP array functions to query the arrays without having to write loops with nested conditions. If this was a MySQL database and I wanted to find the sum of spend from accounts with active status and weekly plans I would simply run the following query
SELECT SUM('Spend') FROM table WHERE status = 'Active' AND plan = 'Weekly';
But instead I have to take the following approach using a for loop
for ($index = 1; $index < count($table); $index++){
if (($table['status'][$index] == 'active') && ($table['plan'][$index] == 'weekly')){
$spend[$index] = $table['spend'][$index];
}
}
echo array_sum($spend);
This approach gives me a headache. Is there an obvious solution for refactoring this into php's native array functions or is a mess of explicit loops inevitable?
There aren't any native functions to do what you want. What's wrong with storing the information from the CSV files into a database?
If that's simply not an option then try foreach loops, they're much cleaner.
$spend = array();
foreach ($table['spend'] as $key => $amount)
{
if ($table['status'][$key] == 'active' && $table['plan'][$key] == 'weekly')
{
$spend[] = $amount;
}
}
Whilst it doesn't solve the loops issue it does help clean them up so you don't lose your mind so much.
Using Array Keys to get all "active" keys, then loop through only those to find the matches.
$keys = array_keys($table['status'], 'active');
foreach($keys as $key)
{
if($table['plan'][$key] == 'weekly')
{
$spend[] = $table['spend'][$key];
}
}
print_r($spend);
Spend:
Array
(
[0] => 19,000
)
this is pretty messy but I found one way of doing it
$keys = array_intersect(array_keys($table['status'], 'active'), array_keys($table['plan'], 'weekly'));
$subs['total'] = array_intersect_key($table['spend'], array_flip($keys));
print_r(array_sum($subs['total']));

Two PHP arrays, unset positions that are only present in one array

I have two arrays (in PHP):
ArrayA
(
[0] => 9
[1] => 1
[2] => 2
[3] => 7
)
ArrayB
(
[0] => 1
[1] => 1
[3] => 8
)
I want to make two new arrays, where I have only the elements declared in both of the arrays, like the following:
ArrayA
(
[0] => 9
[1] => 1
[3] => 7
)
ArrayB
(
[0] => 1
[1] => 1
[3] => 8
)
In this example ArrayA[2] doesn't exist, so ArrayB[2] has been unset.
I wrote this for loop:
for ($i = 0, $i = 99999, $i++){
if (isset($ArrayA[$i]) AND isset($ArrayB[$i]) == FALSE)
{
unset($ArrayA[$i],$ArrayB[$i]);
}
}
But it's not great because it tries every index between 0 and a very big number (99999 in this case). How can I improve my code?
The function you're looking for is array_intersect_key:
array_intersect_key() returns an array containing all the entries of array1 which have keys that are present in all the arguments.
Since you want both arrays, you'll have to run it twice, with the parameters in opposite orders, as it only keeps keys from the first array. An example:
$arrayA_filtered = array_intersect_key($arrayA, $arrayB);
$arrayB_filtered = array_intersect_key($arrayB, $arrayA);
Also, although a for loop wasn't ideal in this case, in other cases where you find yourself needing to loop through sparse array (one where not every number is set), you can use a foreach loop:
foreach($array as $key => $value) {
//Do stuff
}
One very important thing to note about PHP arrays is that they are associative. You can't simply use a for loop, as the indices are not necessarily a range of integers. Consider what would happen if you applied this algorithm twice! You'd get out of bounds errors as $arrayA[2] and $arrayB[2] no longer exist!
I would iterate through the arrays using nested foreach statements. I.e.
$outputA = array();
$outputB = array();
foreach ($arrayA as $keyA => $itemA) {
foreach ($arrayB as $keyB => $itemB) {
if ($keyA == $keyB) {
$outputA[$keyA] = $itemA;
$outputB[$keyB] = $itemB;
}
}
This should give you two arrays, $outputA and $outputB, which look just like $arrayA and $arrayB, except they only include key=>value pairs if the key was present in both original arrays.
foreach($arrayA as $k=>$a)
if (!isset($arrayB[$k]))
unset($arrayA[$k];
Take a look to php : array_diff
http://docs.php.net/manual/fr/function.array-diff.php

How to find the index number of an array that contains a certain key/value

(
[1] => Array
(
[rules_properties_id] => 1
[operator] => >=
[value] => 2
[function] => NumOrdersPlaced
[rules_properties_params] => Array
(
[num_days] => 30
[customer_id] => 5
)
)
[2] => Array
(
[rules_properties_id] => 1
[operator] => >=
[value] => 5
[function] => NumOrdersPlaced
[rules_properties_params] => Array
(
[num_days] => 90
[customer_id] => 5
)
)
[3] => Array
(
[rules_properties_id] => 2
[operator] => >
[value] => 365
[function] => CustAcctAge
[rules_properties_params] => Array
(
[customer_id] => 5
)
)
)
That's the print_r of an array that I'm getting back from my database. I need to find the index number of the sub-array that contains the function called NumOrdersPlaced (the expected result would be 2.) Is the only way to do this by looping through the array and subarrays and comparing (as in this answer)? Or is there a more efficient, elegant (i.e. one-liner) function available that I don't know about?
No, there is no one liner for searching multidimensional arrays in PHP, expect you'll write a function for it yourself and use it as one liner :)
Approaches would be:
Change the data structure to somewhat that is good for search operations. eg xml with xpath
When creating the array from your question, create another array which indexes are the function names, and which values are pointers to the sub arrays of the original array
Use the database for that operations. It's optimized for it
...
When searching for the 'right' way you'll have to find a compromise between performance of search, insert, update, remove operations, memory consumption and ease of usage.
As you asked for a PHP solution, here comes an example how I would do it in PHP using and additional index array: (approach 2. from the list above)
// we need two arrays now:
$data = array();
$index = array();
// imagine you loop through database query results
foreach($db_result as $record) {
// create a copy of $record as the address
// of record will contain the last(!) element agter foreach
$item = $record;
// store pointers to that array in data and index
$data []= &$item;
$index[$item->function] = &$item;
}
// here is your one liner
$found = isset($index['NumOrdersPlaced']) ? $index['NumOrdersPlaced'] : NULL;
// another index seach:
$found = isset($index['CustAcctAge']) ? $index['CustAcctAge'] : NULL;
// note there is no additonal loop. The cost is the
// additional memory for $index

How to remove ALL duplicate values from an array in PHP?

I want to check only the value [id] for duplicates, and remove all keys where this "field" [id] is a duplicate.
Example: If I have numbers 1,2,1. I want the result to be 2, not 1,2. And criteria for duplicates is determined only by checking [id], not any other "field".
Original array:
Array
(
[0] => Array
(
[name] => John
[id] => 123
[color] => red
)
[1] => Array
(
[name] => Paul
[id] => 958
[color] => red
)
[2] => Array
(
[name] => Jennifer
[id] => 123
[color] => yellow
)
)
The result I want:
Array
(
[0] => Array
(
[name] => Paul
[id] => 958
[color] => red
)
)
I agree with everyone above, you should give us more information about what you've tried, but I like to code golf, so here's a completely unreadble solution:
$new_array = array_filter($array, function($item) use (&$array){
return count(array_filter($array, function($node) use (&$item){
return $node['id'] == $item['id'];
})) < 2;
});
This should be fairly easy to accomplish with a couple of simple loops:
set_time_limit(0); // Disable time limit to allow enough time to process a large dataset
// $items contains your data
$id_counts = array();
foreach ($items as $item) {
if (array_key_exists($item['id'], $id_counts)) {
$id_counts[$item['id']]++;
} else {
$id_counts[$item['id']] = 1;
}
}
for ($i = count($items); $i >= 0; $i--) {
if ($id_counts[$items[$i]['id']] > 1) {
array_splice($items, $i, 1);
}
}
Result:
Array
(
[0] => Array
(
[name] => Paul
[id] => 958
[color] => red
)
)
While there are neater ways to do it, one advantage of this method is you're only creating new arrays for the list of ids and duplicate ids and the array_splice is removing the duplicates from the original array, so memory usage is kept to a minimum.
Edit: Fixed a bug that meant it sometimes left one behind
This is a very basic approach to the answer and I am sure there are much better answers however I would probably start by doing it the way I would on paper.
I look at the first index, check its value. Then I go through every other index making note of their index if the value is the same as my originally noted value. Once I have gone through the list if I have more than one index with that particular value I remove them all (starting with the highest index, so as to not affect indexes of the others while deleting).
Do this for all other indexes till you reach the end of the list.
It is long winded but will make sure it removes all values which have duplicates. and leaves only those which originally had no duplicates.
function PickUniques(array $items){
// Quick way out
if(empty($items)) return array();
$counters = array();
// Count occurences
foreach($items as $item){
$item['id'] = intval($item['id']);
if(!isset($counters[$item['id']])){
$counters[$item['id']] = 0;
}
$counters[$item['id']]++;
}
// Pop multiples occurence ones
foreach($counters as $id => $occurences){
if($occurences > 1){
unset($counters[$id]);
}
}
// Keep only those that occur once (in $counters)
$valids = array();
foreach($items as $item){
if(!isset($items[$item['id']])) continue;
$valids[$item['id']] = $item;
}
return $valids;
}
Try this one :)

Combine array items by name with a separator?

I have the following PHP code which runs a MySQL query and returns the results as an array:
function mysql_resultTo2DAssocArray ( $result) {
$i=0;
$ret = array();
while ($row = mysql_fetch_assoc($result)) {
foreach ($row as $key => $value) {
$ret[$i][$key] = $value;
}
$i++;
}
return ($ret);
}
$compare = mysql_resultTo2DAssocArray(mysql_query("SELECT Temp.School, Temp.Status, Snow.Token FROM Temp
JOIN Snow ON Temp.School = Snow.School"));
The results of this look like:
Array
(
[0] => Array
(
[School] => School Name 1
[Status] => Delayed Two Hours
[Token] => Token1
)
[1] => Array
(
[School] => School Name 1
[Status] => Delayed Two Hours
[Token] => Token2
)
)
Is it possible to combine the Token items by a comma within those arrays if the School item is the same for each?
One thing is that it's possible for there to be more arrays within the global array, with different School names, that shouldn't be grouped but left alone. Basically, is it possible to ONLY group when School is the same. It's also possible for there to be more than two tokens.
SELECT Temp.School, Temp.Status, GROUP_CONCAT(Snow.Token) Tokens
FROM Temp JOIN Snow USING (School)
GROUP BY Temp.School
This isn't completely valid, because the Status column is not aggregated or part of the GROUP BY; however MySQL normally allows it (there's an option to disallow it, I don't remember the name offhand). If there are rows with different Status values, it will pick one of them arbitrarily; you didn't say what should happen with different Statuses.

Categories