Merge and diff arrays of arrays to find common values - php

I am trying to loop through several database table structures and determine the common structure (ie what columns are identical). The final structure should only show common columns, so if any table has a unique column, it should not be in the final array.
This is an example of what three table structures may look like.
$arr1 = [
["name"=>"col1", "type"=>"varchar"],
["name"=>"col2", "type"=>"int"]
];
$arr2 = [
["name"=>"col1", "type"=>"varchar"],
["name"=>"col2", "type"=>"int"] ,
["name"=>"col3", "type"=>"date"]
];
$arr3 = [
["name"=>"col1", "type"=>"varchar"],
["name"=>"col3", "type"=>"int"]
];
$arrays = [$arr1, $arr2, $arr3];
Using array_merge, array_diff, array_intersect, or a loop, is it possible to determine which of these columns are common to all tables?
The end value should be
[["name"=>"col1", "type"=>"varchar"]]

You could use array_uintersect, which does the intersection with your own function. But you should keep in mind, that the compare function does not simply returns true or false - instead you have to return -1, 0 or 1.
In PHP7 you could use the spaceship operator <=> for the comparison.
$intersection = array_uintersect($arr1, $arr2, $arr3, function($a, $b) {
$ret = $a['name'] <=> $b['name'];
return $ret ? $ret : $a['type'] <=> $b['type'];
});
print_r($intersection);
If you want to put all arrays inside the intersection, you could do this:
$arrays = [$arr1, $arr2, $arr3];
$arrays[] = function($a, $b) {
$ret = $a['name'] <=> $b['name'];
return $ret ? $ret : $a['type'] <=> $b['type'];
};
$intersection = array_uintersect(...$arrays);
In older versions, you should instead use strcasecmp.

You can use a custom compare method with array_uintersect():
$arr1 = [
["name" => "col1", "type" => "varchar"],
["name" => "col2", "type" => "int"]
];
$arr2 = [
["name" => "col1", "type" => "varchar"],
["name" => "col2", "type" => "int"],
["name" => "col3", "type" => "date"]
];
$arr3 = [
["name" => "col1", "type" => "varchar"],
["name" => "col3", "type" => "int"]
];
$common_columns = array_uintersect($arr1, $arr2, $arr3, 'compareDeepValue');
print_r($common_columns);
function compareDeepValue($val1, $val2)
{
return (strcasecmp(serialize($val1), serialize($val2))) ;
}
Will output:
Array
(
[0] => Array
(
[name] => col1
[type] => varchar
)
)
Note:
#Abracadaver made a good point this method will only work correctly when you have the array conventions in the same order.
Than you can for example use this:
function compareDeepValue($val1, $val2)
{
return ($val1['name'] === $val2['name'] && $val1['type'] === $val2['type']) ? 0 : -1;
}

You can extract the arrays and index by the name key and compute the intersection using the keys:
$result = array_intersect_key(array_column($arr1, null, 'name'),
array_column($arr2, null, 'name'),
array_column($arr3, null, 'name'));
Yields:
Array
(
[col1] => Array
(
[name] => col1
[type] => varchar
)
)
If needed, use array_values to get back to numeric indexes.

Maybe ?
$arrays = [$arr1, $arr2, $arr3];
$arrays_extended = [];
foreach($arrays as $row => $innerArray){
foreach($innerArray as $innerRow => $value){
array_push($arrays_extended, $value);
}
}
var_dump(array_unique($arrays_extended));
Outputs [["name"=>"col1", "type"=>"varchar"]]

Approach:
1.convert elements to strings as follows:
array(2) {
[0] =>
string(32) "{"name":"col1","type":"varchar"}"
[1] =>
string(28) "{"name":"col2","type":"int"}"
}
array(3) {
[0] =>
string(32) "{"name":"col1","type":"varchar"}"
[1] =>
string(28) "{"name":"col2","type":"int"}"
[2] =>
string(29) "{"name":"col3","type":"date"}"
}
array(2) {
[0] =>
string(32) "{"name":"col1","type":"varchar"}"
[1] =>
string(28) "{"name":"col3","type":"int"}"
}
2.Use array intersect to find common elements
3.convert back to arrays.
$arr1 = [
["name"=>"col1", "type"=>"varchar"],
["name"=>"col2", "type"=>"int"]
];
$arr2 = [
["name"=>"col1", "type"=>"varchar"],
["name"=>"col2", "type"=>"int"] ,
["name"=>"col3", "type"=>"date"]
];
$arr3 = [
["name"=>"col1", "type"=>"varchar"],
["name"=>"col3", "type"=>"int"]
];
list($darr1, $darr2, $darr3) = convertArrToStr($arr1, $arr2, $arr3);
/* output:
array(2) {
[0] =>
string(32) "{"name":"col1","type":"varchar"}"
[1] =>
string(28) "{"name":"col2","type":"int"}"
}
array(3) {
[0] =>
string(32) "{"name":"col1","type":"varchar"}"
[1] =>
string(28) "{"name":"col2","type":"int"}"
[2] =>
string(29) "{"name":"col3","type":"date"}"
}
array(2) {
[0] =>
string(32) "{"name":"col1","type":"varchar"}"
[1] =>
string(28) "{"name":"col3","type":"int"}"
}
*/
var_dump(duplicates($darr1, $darr2, $darr3));
/* output:
array(1) {
[0] =>
array(2) {
'name' =>
string(4) "col1"
'type' =>
string(7) "varchar"
}
}
*/
function convertArrToStr() {
$args = func_get_args();
foreach($args as &$arg){
foreach($arg as $k => $arr) {
$arg[$k] = json_encode($arr, true);
}
}
return $args;
}
function duplicates($darr1, $darr2, $darr3) {
$intersects = array_intersect($darr1, $darr2, $darr3);
$r = [];
foreach($intersects as $v) {
$r[] = json_decode($v, true);
}
return $r;
}
Hope this helps you write a more elegant solution.

Related

Filter array by keys and populate new multi-dimensional array from mutated keys and original values

I need to extract data from elements with keys that start with foo. from the below array:
[
'name' => 'Bar',
'location' => 'Baz',
'foo.2021-02-01' => '50000.00',
'foo.2021-03-01' => '50000.00',
'foo.2021-04-01' => '50000.00',
'foo.2021-05-01' => '',
]
After identifying qualifying keys, I need to create a new indexed array of associative rows using the date substring from the original keys like so:
[
['date' => '2021-02-01', 'value' => '50000.00'],
['date' => '2021-03-01', 'value' => '50000.00'],
['date' => '2021-04-01', 'value' => '50000.00'],
['date' => '2021-05-01', 'value' => ''],
]
I've been able to extract the keys like so:
$keys = array_keys($theData[0]);
foreach ( $keys as $key ) {
if ( preg_match( '/foo.*/', $key ) ) {
$line = explode('.', $key);
$item[]['name'] = $line[1];
}
}
but I'm losing the values.
I then tried looping through the array manually and rebuilding the desired outcome, but the keys will change so I don't know how future-proof that would be.
Is there a wildcard approach I can take to achieve this?
You almost had it:
<?php
$theData = [
'name' => 'Bar',
'location' => 'Baz',
'foo.2021-02-01' => '50000.00',
'foo.2021-03-01' => '50000.00',
'foo.2021-04-01' => '50000.00',
'foo.2021-05-01' => ''
];
$item = [];
// No need for array_keys(), foreach() can already do this
foreach( $theData as $key => $value )
{
// check if the key starts with foo.
// Regular expressions are heavy; if you'd like then substitute with:
// if ( substr( $key, 0, 4 ) === 'foo.' )
if ( preg_match( '/^foo\\./', $key ) )
{
// foo. is 4 chars long so substring from the fourth index till the end
$item[] = [
'date' => substr( $key, 4 ),
'value' => $value
];
}
}
var_dump( $item );
Output:
array(4) {
[0]=>
array(2) {
["date"]=>
string(10) "2021-02-01"
["value"]=>
string(8) "50000.00"
}
[1]=>
array(2) {
["date"]=>
string(10) "2021-03-01"
["value"]=>
string(8) "50000.00"
}
[2]=>
array(2) {
["date"]=>
string(10) "2021-04-01"
["value"]=>
string(8) "50000.00"
}
[3]=>
array(2) {
["date"]=>
string(10) "2021-05-01"
["value"]=>
string(0) ""
}
}
A simple loop, checking for the key starting with foo. and then a little code to replace foo. in the key with nothing will do the trick
If you have PHP8 or >
$arr = [
'name' => 'Bar',
'location' => 'Baz',
'foo.2021-02-01' => '50000.00',
'foo.2021-03-01' => '50000.00',
'foo.2021-04-01' => '50000.00',
'foo.2021-05-01' => ''
];
$new = [];
foreach ($arr as $k => $v){
if ( str_starts_with( $k , 'foo.' ) ) {
$new[] = ['date' => str_replace('foo.', '', $k), 'value' => $v];
}
}
print_r($new);
RESULT
Array
(
[0] => Array
([date] => 2021-02-01, [value] => 50000.00)
[1] => Array
([date] => 2021-03-01, [value] => 50000.00)
[2] => Array
([date] => 2021-04-01, [value] => 50000.00)
[3] => Array
([date] => 2021-05-01, [value] => )
)
Alternatively, for PHP versions prior to PHP8
$new = [];
foreach ($arr as $k => $v){
if ( strpos( $k , 'foo.') !== FALSE && strpos( $k , 'foo.') == 0 ) {
$new[] = ['date' => str_replace('foo.', '', $k), 'value' => $v];
}
}
Using str_starts_with and explode
$arr = [];
foreach ($theData as $k => $v){
if (str_starts_with($k, "foo."))
$arr[] = ["date" => explode(".", $k)[1], "value" => $v];
}
var_dump($arr);
sscanf() is an ideal function to call which will both check for qualifying strings and extract the desired trailing date value. It doesn't use regex, but it does require a placeholder %s to target the date substring. If a given string doesn't qualify, no element is pushed into the result array.
Code: (Demo) (without compact())
$result = [];
foreach ($array as $key => $value) {
if (sscanf($key, 'foo.%s', $date)) {
// $result[] = ['date' => $date, 'value' => $value];
$result[] = compact(['date', 'value']);
}
}
var_export($result);
If you remove the optional technique of using compact(), this solution makes fewer function calls than all other answers on this page.
I would probably only use regex if I wanted to strengthen the validation for qualifying key strings. (Demo)
$result = [];
foreach ($array as $key => $value) {
if (preg_match('~^foo\.\K\d{4}-\d{2}-\d{2}$~', $key, $m)) {
$result[] = ['date' => $m[0], 'value' => $value];
}
}
var_export($result);

How to find the value of a multidimensional array by key

How I can search array key from multidimensional array and return found key value with parent array key if it's exist. For example I've custom array:
$array = [
'type' => 'vacancy',
'needs' => ['root' => 'active'],
'market' => 'shopping',
'red' => 'color',
'education' => 'learning',
'fruits' => [
'red' => 'apple',
'cool' => 'cherry'
]
];
For example I need search key red from current array. As you see in this array exist 2 items with key red. If array have similar keys then function return example array as response:
[
0 => ['red' => 'color'],
1 => ['red' => 'apple']
]
If search key (example type) is only once inside array then response will be like this:
['type' => 'vacancy']
I tired:
function searchKey($key, $array) {
foreach ($array as $k => $v) {
if($key === $k) {
return [$k => $v];
} elseif(is_array($v)) {
return searchKey($key, $v);
} elseif(is_array($k)) {
return searchKey($key, $k);
}
}
return false;
}
When I search key root from array result is correct but when I searching key red return false. How can be solved my function or has any performance methods for searching key and get result for my needs?
You can try this :
function findByKey($findKey, $array, $result = []) {
foreach ($array as $key => $value) {
if ($key === $findKey) {
$result[] = [$key => $value];
}
if (is_array($value)) {
$result = findByKey($findKey, $value, $result);
}
}
return $result;
}
The idea is to use a recursive function :
you loop through your array
for each key => value, you check if the key is what you want : if yes, add it to the result array, else go next
if the value is an other array, you search inside this array if you have the key you want
Now use it :
$array = [
'type' => 'vacancy',
'needs' => ['root' => 'active'],
'market' => 'shopping',
'red' => 'color',
'education' => 'learning',
'fruits' => [
'red' => 'apple',
'cool' => 'cherry'
]
];
With key type :
$result = findByKey('type', $array);
var_dump($result);
Output is :
array(1) {
[0]=>
array(1) {
["type"]=>
string(7) "vacancy"
}
}
With key red :
$result = findByKey('red', $array);
var_dump($result);
Output is :
array(2) {
[0]=>
array(1) {
["red"]=>
string(5) "color"
}
[1]=>
array(1) {
["red"]=>
string(5) "apple"
}
}
Here is a link to test it : link

PHP: Make an array clear?

I have an array like this:
array(
[cat] => news,
[comments_count] => 2,
[meta] => array(
[first_meta] => 44,
[second_meta] => 54,
)
)
The above code is an example of array that I have. Now I wanna make the above array clear like this:
array(
[cat] => news,
[comments_count] => 2,
[first_meta] => 44,
[second_meta] => 54,
)
(means Delete -meta- but not it's indexes. I want to add indexes of meta to the first array)
Add the meta array to the array and then unset the meta array:
$array = $array + $array['meta'];
unset($array['meta']);
You may use the below function if you have a multidimentional array and you can reuse it anywhere.
function array_flatten($array) {
if (!is_array($array)) {
return false;
}
$result = array();
foreach ($array as $key => $value) {
if (is_array($value)) {
$result = array_merge($result, array_flatten($value));
} else {
$result[$key] = $value;
}
}
return $result;
}
$array = array(
'cat' => 'news',
'comments_count' => '2',
'meta' => array(
'first_meta' => '44',
'second_meta' => '54',
)
);
var_dump(array_flatten($array));
The result will be
array(4) {
["cat"]=>
string(4) "news"
["comments_count"]=>
string(1) "2"
["first_meta"]=>
string(2) "44"
["second_meta"]=>
string(2) "54"
}
Otherwise if you just need to flatten meta array as in your question. array_merge() the meta array and unset meta it as below.
$result = array_merge($array, $array["meta"]);
unset($result["meta"]);
var_dump($result);

PHP - sum values with the same key

What is the best way to sum the 'val' field with the same 'color' for each different color:
Array
(
[0] => Array
(
[color]=> "red"
[val]=> 4
)
[1] => Array
(
[color]=> "green"
[val]=> 3
)
[2] => Array
(
[color]=> "blue"
[val]=> 1
)
[3] => Array
(
[color]=> "green"
[val]=> 6
)
[4] => Array
(
[color]=> "blue"
[val]=> 2
)
)
Desired result : red: 4; green: 9; blue: 3.
Regards,
Elio Fernandes
I would do it this way, with a foreach loop:
$temp = [];
foreach($arr as $value) {
//check if color exists in the temp array
if(!array_key_exists($value['color'], $temp)) {
//if it does not exist, create it with a value of 0
$temp[$value['color']] = 0;
}
//Add up the values from each color
$temp[$value['color']] += $value['val'];
}
You can use array_reduce to have less and more readable code:
$array = array
(
0 => array("color"=> "red","val"=> 4),
1 => array("color"=> "blue","val"=> 3),
2 => array("color"=> "blue","val"=> 1)
);
function sum($accumulator, $item){
$accumulator[$item['color']] = $accumulator[$item['color']] ?? 0;
$accumulator[$item['color']] += $item['val'];
return $accumulator;
}
$sum = array_reduce($array, "sum");
var_dump($sum); // return here array(2) { ["red"]=> int(4) ["blue"]=> int(4) }
And the documentation: http://php.net/manual/en/function.array-reduce.php
I would loop it for readability's sake.
$output = array();
foreach($array as $value){
if(!isset($output[$value['color']])) $output[$value['color']] = 0; //Ensure the value is 0 if it doesn't exist
$output[$value['color']] += $value['val']; //Add the total for that color
}
This will return an array of colors to total counts.
Personally I'd do something like this with array_reduce, but as mentioned above it might not be the most readable option. Definitely a matter of opinion though.
$result = array_reduce($array, function ($carry, $item) {
$carry[$item['color']] = $carry[$item['color']] ?? 0;
$carry[$item['color']] += $item['val'];
return $carry;
}, []);
See https://eval.in/894644
Edit: Fix notices
To keep up the speed, without the foreach loop, I would say to do it like this:
$array = [
[
"color" => "red",
"val" => "4"
],
[
"color" => "green",
"val" => "3"
],
[
"color" => "blue",
"val" => "1"
],
[
"color" => "green",
"val" => "6"
],
[
"color" => "blue",
"val" => "2"
]
];
$array = array_values(array_reduce(
$array,
function (array $a, array $v) {
$k = "".$v['color']."";
if (!array_key_exists($k, $a)) {
$a[$k] = $v;
} else {
$a[$k]['val'] += $v['val'];
}
return $a;
},
array()
));
var_dump($array);
$arrays = array(
array(
"color"=>"red",
"val"=>4
),
array(
"color"=>"green",
"val"=>3
)
);
foreach ($color as $array) {
echo $array["color"].": ".$array["val"].";";
}

Sorting an associative array

I have an array $array
$array =>
(
['name'] => (
"Ronaldo","Ribery","Bale","Messi"
),
['rank'] => (
2,4,1,3
)
)
Now How can i sort the array DESC using rank field along with the name
Expected output ->
$array =>
(
['name'] => (
"Ribery","Messi","Ronaldo","Bale"
),
['rank'] => (
4,3,2,1
)
)
A valid use for oft-misunderstood array_multisort()
<?php
$array = [
'name' => ["Ronaldo","Ribery","Bale","Messi"],
'rank' => [2,4,1,3]
];
array_multisort(
$array['rank'], SORT_DESC, SORT_NUMERIC,
$array['name'], SORT_ASC, SORT_STRING
);
var_dump($array);
array(2) {
'name' =>
array(4) {
[0] =>
string(6) "Ribery"
[1] =>
string(5) "Messi"
[2] =>
string(7) "Ronaldo"
[3] =>
string(4) "Bale"
}
'rank' =>
array(4) {
[0] =>
int(4)
[1] =>
int(3)
[2] =>
int(2)
[3] =>
int(1)
}
}
Check out this php article also if you want your two arrays to have a relation than you could better write it like this:
array(
array(
name => 'Messi',
rank => 4,
),
etc..
);
Why not change your data structure to be able to work with objects?
class Player {
public $name; // would be better as private and add getters
public $rank;
public function __construct($name, $rank) {
$this->name = $name;
$this->rank = $rank;
}
}
Then fill your array (I'm not familiar with soccer):
$players = [ // or array(...) if you are using older PHP
new Player('Foo', 3),
new Player('Bar', 1),
new Player('FooBar', 2)
];
Then you can use the regular sort:
// or a function name, if using older PHP
usort($players, function (Player $a, Player $b) {
if ($a->rank == $b->rank) {
return 0;
}
return ($a->rank < $b->rank) ? -1 : 1;
});

Categories