how can I check and delete duplicate arrays?
Example:
$a = array(
array(
'id' => 1,
'name' => 'test'
),
// Next array is equal to first, then delete
array(
'id' => 1,
'name' => 'test'
),
// Different array, then continue here
array(
'id' => 2,
'name' => 'other'
)
);
If the array is the same, then delete the duplicate and get only one array.
You can use a lookup table storing serialized arrays. If an array already exists in the lookup table, you have a duplicate and can splice out the key:
$a = array(
array(
'id' => 1,
'name' => 'test'
),
array(
'id' => 1,
'name' => 'test'
),
array(
'id' => 2,
'name' => 'other'
)
);
$seen = [];
for ($i = count($a) - 1; $i >= 0; $i--) {
if (array_key_exists(json_encode($a[$i]), $seen)) {
array_splice($a, $i, 1);
}
else {
$seen[json_encode($a[$i])] = 1;
}
}
print_r($a);
Output:
Array
(
[0] => Array
(
[id] => 1
[name] => test
)
[1] => Array
(
[id] => 2
[name] => other
)
)
Try it!
array_unique()
Example:
$array = array(1, 2, 2, 3);
$array = array_unique($array); // Array is now (1, 2, 3)
You can loop through the parent array, serialize each child array and save it in a third array, and as you are looping, check for the existence of the serial of each next child array to all previous ones saved in the third array. If it exists, remove the current duplicate from the parent array by key. The function below demonstrates this.
function remove_duplicate_nested_arrays($parent_array)
$temporary_array = array(); // declare third, temporary array.
foreach($parent_array as $key => $child_array){ // loop through parent array
$child_array_serial = serialize($child_array); // serialize child each array
if(in_array($child_array_serial,$temporary_array)){ // check if child array serial exists in third array
unset($parent_array[$key]); // unset the child array by key from parent array if it's serial exists in third array
continue;
}
$temporary_array[] = $child_array_serial; // if this point is reached, the serial of child array is not in third array, so add it so duplicates can be detected in future iterations.
}
return $parent_array;
}
This can also be achieved in 1 line, using #Jose Carlos Gp suggestion as follows:
$b = array_map('unserialize', array_unique(array_map('serialize', $a)));
The function above kind of expands on what is actually happening in the 1 liner solution.
Related
I want to convert my Associate Array into array based on certain criteria:
It should contain multiple column (Hence, can't user array_column in this case.)
It should ADD duplicate date
It should remove particular sub-array if NULL. (array_filter)
Input :
array (
0 =>
array (
'date' => "2019-03-31",
'a' => '1',
'b' => '1',
),
1 =>
array (
'date' => "2019-04-02", // If "SAME" date then ADD them
'a' => '1',
'b' => '1',
),
2 =>
array (
'date' => "2019-04-02", // If "SAME" date then ADD them
'a' => '2',
'b' => '1',
),
3 =>
array (
'date' => "2019-04-10",
'a' => '', // If "NULL" then remove the particular sub-array
'b' => '1',
),
4 =>
array (
'date' => "2019-04-18",
'a' => '4',
'b' => '10',
),
)
I've tried the following,
Although I was able to select multiple column in array but this is giving me:
"Duplicate" date.
And including sub-array which has "Null" value.
function colsFromArray(array $array, $keys)
{
if (!is_array($keys)) $keys = [$keys];
return array_map(function ($el) use ($keys) {
$o = [];
foreach($keys as $key){
$o[$key] = isset($el[$key])?$el[$key]:false;
}
return $o;
}, $array);
}
$result = colsFromArray($arr, array("date", "a"));
Required Output :
array (
0 =>
array (
'date' => "2019-03-31",
'a' => '1',
),
1 =>
array (
'date' => "2019-04-02",
'a' => '3',
),
2 =>
array (
'date' => "2019-04-18",
'a' => '4',
),
)
You could try something like this:
function colsFromArray(array $array, $keys)
{
if (!is_array($keys)) $keys = [$keys];
$returnArray = [];
foreach($array as $in){
foreach($keys as $key){
//Check if the key already has a value
if(isset($returnArray[$in['date']][$key]) && is_numeric($returnArray[$in['date']][$key])){
$returnArray[$in['date']][$key] += $in[$key];
}
//If the key is empty, continue
elseif(empty($in[$key])){
continue;
}
//If value is to be set, set date as well
else{
$returnArray[$in['date']]['date'] = $in['date'];
$returnArray[$in['date']][$key] = $in[$key];
}
}
}
//Return array after resetting indexes and removing empty entries
return array_values(array_filter($returnArray));
}
The way that you are passing in the array of columns to your customer function is ambiguous about what should be used to group by and what should be summed. OR if you will never have more than two elements in your 2nd parameter, then your custom function lacks flexibility.
Notice how #SverokAdmin's answer has date hardcoded in the logic, but there is no guarantee that this column name will always exist in the first parameter $array. Ergo, the utility function cannot be reliably used as a utility function.
Consider this custom function which provides your desired result, but allows more meaningful parameter declarations. My snippet allows you to group by one or more column, keep one or more columns in the subarrays, and specify which column should be summed. I cannot make the last column iterable because you state that if a column is missing a value, it should be omitted from the result. In other words, your desired result is unknown if you wanted to sum a and b, but one of the a values was empty.
Code: (Demo)
function colsFromArray(array $array, $groupBy, $keepables, $summable): array
{
$groupBy = (array)$groupBy;
$keepables = (array)$keepables;
// cannot make $summable iterable type because question is unclear on the desired outcome when one of the columns is empty
return array_values(
array_reduce(
$array,
function ($result, $row) use($groupBy, $keepables, $summable) {
$uniqueKey = [];
foreach ($groupBy as $col) {
$uniqueKey[] = $row[$col];
}
$uniqueKey = implode('_', $uniqueKey);
if (!$row[$summable]) {
return $result;
}
foreach ($keepables as $keepable) {
$result[$uniqueKey][$keepable] = $row[$keepable];
}
$result[$uniqueKey][$summable] = ($result[$uniqueKey][$summable] ?? 0) + $row[$summable];
return $result;
},
[]
)
);
}
var_export(
colsFromArray($arr, 'date', 'date', 'a')
);
Output:
array (
0 =>
array (
'date' => '2019-03-31',
'a' => 1,
),
1 =>
array (
'date' => '2019-04-02',
'a' => 3,
),
2 =>
array (
'date' => '2019-04-18',
'a' => 4,
),
)
I am trying to find the difference of 2 multidimensional arrays. I am attempting to solve this with a modified recursive array difference function.
If I have the following array setup:
$array1 = array(
0 => array(
'Age' => '1004',
'Name' => 'Jack'
),
1 => array (
'Age' => '1005',
'Name' => 'John'
)
);
$array2 = array(
0 => array(
'Age_In_Days' => '1004',
'Name' => 'Jack'
),
1=> array(
'Transaction_Reference' => '1005',
'Name' => 'Jack'
)
);
I am trying to match the arrays however the keys are not the same. I want to return the difference between the two multidimensional arrays where
$array1[$i]['Age'] == $array2[$i]['Age_In_Days'];
I want to keep the original array structure if the above condition holds true so the output I am looking for is:
$diff = array (1 => array (
'Age' => '1005',
'Name' => 'John'
));
However I am having issues with how to modify the recursive function to achieve this. Any help is appreciated! Thanks!
You need to loop through first array and compare values with second array. Then follows your condition. If condition is true then push this unique value to third array. Values in third array are now diff between first and second array.
$diff = [];
foreach ($array1 as $value1) {
foreach ($array2 as $value2) {
if ($value1['Age'] !== $value2['Age_In_Days']) {
array_push($diff, $value1);
}
}
}
I use this function to display any kind of multi-dimensional array as a human readeable html table. For exemple, this array :
$arr = array
(
'root' => array(
'main' => array (
'members' => array(
array ('identifier' => '36', 'fullname' => 'jonathan carter'),
array ('identifier' => '42', 'fullname' => 'hello world')
)
)
)
);
becomes a table (with echo array2table($arr, true)) :
My problem, as you can see, is that there are too many table headers when multiple arrays contains only one sub array. This is what I would like to have :
You probably say: "What did you tried ?"
Well, before displaying the table header, I made and call a recursive function which returns an array of keys if there nested tables :
function nextTableHeaders($array)
{
// requires an array with only 1 child
if (!is_array($array) || count($array) !== 1)
return array();
$key = key($array);
return array_merge(array($key), nextTableHeaders($array[$key]));
}
In that case, it returns :
array('root', 'main', 'members');
Then I join the values with ' > ' and it becomes my new table headers. But I need then to go deeper into the arrays (because I don't wan't to display "main > members", and "members"). But I didn't succeed.
I would appreciate any help.
My suggestion is to keep the table printing as is, and to create a new function that will take the array and then convert it into the sort of array which when printed will display what you wish to be displayed.
function compressArray($array) {
// Non-arrays and multiple-key arrays are unaffected
if (!is_array($array) or count($array) !== 1)
return $array;
// This is a recursive algorithm
$child = compressArray(current($array));
// Compression unnecessary when child is non-array or a multiple-key array
if (!is_array($child) or count($child) !== 1)
return $array;
// Both parent and child are single key arrays so compress
$new_key = key($array) . " > " . key($child);
return array($new_key => current($child));
}
This will convert
$arr = array
(
'root' => array(
'main' => array (
'members' => array(
array ('identifier' => '36', 'fullname' => 'jonathan carter'),
array ('identifier' => '42', 'fullname' => 'hello world')
)
)
)
);
into
$arr = array
(
'root > main > members' => array(
array ('identifier' => '36', 'fullname' => 'jonathan carter'),
array ('identifier' => '42', 'fullname' => 'hello world')
)
);
I have a multi-dimensional array that looks like this:
The base array is indexed based on category ids from my catalog.
$cat[category_id]
Each base array has three underlying elements:
['parent_id']
['sort_order']
['name']
I want to create a function that allows us to create a list of category_id's and names for a given parent_category_id in the correct sort order. Is this possible? Technically it is the same information, but the array is constructed in a weird way to extract that information.
Here is an example definition for the array:
$cat = array();
$cat[32]['parent_id']= 0;
$cat[32]['sort_order']= 1;
$cat[32]['name']= 'my-category-name1';
$cat[45]['parent_id']= 0;
$cat[45]['sort_order']= 0;
$cat[45]['name']= 'my-category-name2';
$cat[2]['parent_id']= 0;
$cat[2]['sort_order']= 2;
$cat[2]['name'] = "my-category-name3";
$cat[3]['parent_id']= 2;
$cat[3]['sort_order']= 1;
$cat[3]['name'] = "my-category-name4";
$cat[6]['parent_id']= 2;
$cat[6]['sort_order']= 0;
$cat[6]['name'] = "my-category-name5";
Assuming it's something of this sort:
$ary = Array(
0 => Array(
'parent_category_id' => null,
'sort_order' => 0,
'name' => 'my-category-name0'
),
1 => Array(
'parent_category_id' => 0,
'sort_order' => 1,
'name' => 'my-category-name1'
),
2 => Array(
'parent_category_id' => 0,
'sort_order' => 2,
'name' => 'my-category-name2'
),
3 => Array(
'parent_category_id' => null,
'sort_order' => 0,
'name' => 'my-category-name3'
),
4 => Array(
'parent_category_id' => 3,
'sort_order' => 0,
'name' => 'my-category-name4'
)
);
You can use a combination of a foreach and usort to achieve what you're going for.
// #array: the array you're searchign through
// #parent_id: the parent id you're filtering by
function getFromParent($array, $parent_id){
$result = Array();
foreach ($array as $category_id => $entry){
if ($entry['parent_category_id']===$parent_id)
$result[$category_id] = $entry;
}
usort($result,create_function('$a,$b','return ($a["sort_order"]>$b["sort_order"]?1:($b["sort_order"]<$a["sort_order"]?-1:0));'));
return $result;
}
var_export(getFromParent($ary,0));
EDIT Sorry, fixed some syntax errors. Tested, and works (at least to result in what I was intending)
EDITv2 Here's the raw output from the above:
array (
0 =>
array (
'parent_category_id' => 0,
'sort_order' => 1,
'name' => 'my-category-name1',
),
1 =>
array (
'parent_category_id' => 0,
'sort_order' => 2,
'name' => 'my-category-name2',
),
)
(Used var_export just for you #FelixKling)
EDITv3 I've updated my answer to go along with the OP's update. I also now make it retain the original "category_id" values in the result array.
First you create an empty array, it will be used to store your result.
$result = array();
You need to iterate through your initial array, you can use foreach().
Then, given your parent_category_id simply use an if statement to check whether it's the given id or not.
If it is, just construct and push your result to your $result array.
Use any of the sort functions you like
Use the magic return $result;
You're done.
function returnSortedParents($categories, $target_parent){
$new_list = array();
foreach($categories as $index => $array){
//FIND ONLY THE ELEMENTS MATCHING THE TARGET PARENT ID
if($array['parent_category_id']==$target_parent){
$new_list[$index = $array['sort_order'];
}
return asort($new_list); //SORT BASED ON THE VALUES, WHICH IS THE SORTING ORDER
}
I have an array of items:
array(
[0] => array(
'item_no' => 1
'item_name' => 'foo
)
[1] => array(
'item_no' => 2
'item_name' => 'bar'
)
) etc. etc.
I am getting another array from a third party source and need to remove items that are not in my first array.
array(
[0] => array(
'item_no' => 1
)
[1] => array(
'item_no' => 100
) # should be removed as not in 1st array
How would I search the first array using each item in the second array like (in pseudo code):
if 'item_no' == x is in 1st array continue else remove it from 2nd array.
// Returns the item_no of an element
function get_item_no($arr) { return $arr['item_no']; }
// Arrays of the form "item_no => position in the array"
$myKeys = array_flip(array_map('get_item_no', $myArray));
$theirKeys = array_flip(array_map('get_item_no', $theirArray));
// the part of $theirKeys that has an item_no that's also in $myKeys
$validKeys = array_key_intersect($theirKeys, $myKeys);
// Array of the form "position in the array => item_no"
$validPos = array_flip($validKeys);
// The part of $theirArray that matches the positions in $validPos
$keptData = array_key_intersect($theirArray, $validPos);
// Reindex the remaining values from 0 to count() - 1
return array_values($keptData);
All of this would be easier if, instead of storing the key in the elements, you stored it as the array key (that is, you'd be using arrays of the form "item_no => item_data") :
// That's all there is to it
return array_key_intersect($theirArray, $myArray);
You can also do:
$my_array =array(
0 => array( 'item_no' => 1,'item_name' => 'foo'),
1 => array( 'item_no' => 2,'item_name' => 'bar')
);
$thrid_party_array = array(
0 => array( 'item_no' => 1),
1 => array( 'item_no' => 100),
);
$temp = array(); // create a temp array to hold just the item_no
foreach($my_array as $key => $val) {
$temp[] = $val['item_no'];
}
// now delete those entries which are not in temp array.
foreach($thrid_party_array as $key => $val) {
if(!in_array($val['item_no'],$temp)) {
unset($thrid_party_array[$key]);
}
}
Working link
If your key is not actually a key of your array but a value, you will probably need to do a linear search:
foreach ($itemsToRemove as $itemToRemove) {
foreach ($availableItems as $key => $availableItem) {
if ($itemToRemove['item_no'] === $availableItem['item_no']) {
unset($availableItems[$key]);
}
}
}
It would certainly be easier if item_no is also the key of the array items like:
$availableItems = array(
123 => array(
'item_no' => 123,
'item_name' => 'foo'
),
456 => array(
'item_no' => 456,
'item_name' => 'bar'
)
);
With this you could use a single foreach and delete the items by their keys:
foreach ($itemsToRemove as $itemToRemove) {
unset($availableItems[$itemToRemove['item_no']]);
}
You could use the following to build an mapping of item_no to your actual array keys:
$map = array();
foreach ($availableItems as $key => $availableItem) {
$map[$availableItems['item_no']] = $key;
}
Then you can use the following to use the mapping to delete the corresponding array item:
foreach ($itemsToRemove as $itemToRemove) {
unset($availableItems[$map[$itemToRemove['item_no']]]);
}