PHP Array Matching - php

I'm semi-new to PHP and looking for a good way to match arrays. Note that I'm going to write my arrays here in python syntax because its way easier to type, but I'm working in PHP.
I have an array that is something like this: {3:4,5:2,6:2}
Then I have an array of arrays, with the inners arrays having the same basic form as the array above. I'd like to write a function that returns all arrays that match the given array, ignoring extra values.
So I want the array above to match {3:4,6:2,5:2} or {3:4,5:2,6:2,7:2}
but not {3:4,5:2} or {3:4,5:2,6:3}
I probably could get it to work, but I doubt the code would be all that great. So I'd love the opinion of better PHP developers.
Thanks

you want array_intersect or array_intersect_key
$a = array(1 => 11, 3 => 33, 2 => 22);
$b = array(3 => 33, 5 => 55, 2 => 22, 1 => 11);
if (array_intersect_key($a, $b) == $a)
echo "b contains a";

I don't think there is a pre-defined function in php that will help with this sort of thing. Just loop through all the arrays and find the ones that match your criteria.
function query( $query, $arrayOfArrays) {
$ret = array();
foreach( $arrayOfArrays as $array) {
if( matches( $array, $query) ) {
$ret[] = $array;
}
}
return $ret;
}
function matches( $array, $query) {
foreach( $query as $key => $value) {
if( !isset( $array[$key]) || $array[$key] != $value) {
return false;
}
}
return true;
}

If you have PHP 5.3 then you can use a closure along with array_filter:
// array to test against
$test = array(
3 => 4,
5 => 2,
6 => 2
);
// array of arrays you need to check
$subjects = array(
array(3 => 4, 5 => 2, 6 => 2),
array(3 => 4, 6 => 2, 5 => 2, 7 => 2),
array(3 => 4, 5 => 2),
array(3 => 4, 5 => 2, 6 => 3),
);
// make $test available within the filter function with use
$result = array_filter($subjects, function($subject) use ($test)
{
// making use of array_intersect_key
// note that $subject must be the first parameter for this to work
return array_intersect_key($subject, $test) == $test;
});
$result will contain only the matching arrays

Related

PHP Array split string and Integers

Below is an array of strings and numbers. How could the string and number values be split into separate arrays (with strings in one array and numbers in another array)?
array('a','b','c',1,2,3,4,5,'t','x','w')
You could also do this in one line using array_filter()
$numbers = array_filter($arr,function($e){return is_numeric($e);});
$alphas = array_filter($arr,function($e){return !is_numeric($e);});
print_r($numbers);
print_r($alphas);
Loop through them, check if is_numeric and add to appropriate array:
$original = array('a','b','c',1,2,3,4,5,'t','x','w');
$letters = array();
$numbers = array();
foreach($original as $element){
if(is_numeric($element)){
$numbers[] = $element;
}else{
$letters[] = $element;
}
}
https://3v4l.org/CAvVp
Using a foreach() like in #jnko's answer will be most performant because it only iterates over the array one time.
However, if you are not concerned with micro-optimization and prefer to write concise or functional-style code, then I recommend using array_filter() with is_numeric() calls, then making key comparisons between the first result and the original array.
Code: (Demo)
$array = ['a','b',0,'c',1,2,'ee',3,4,5,'t','x','w'];
$numbers = array_filter($array, 'is_numeric');
var_export($numbers);
var_export(array_diff_key($array, $numbers));
Output:
array (
2 => 0,
4 => 1,
5 => 2,
7 => 3,
8 => 4,
9 => 5,
)
array (
0 => 'a',
1 => 'b',
3 => 'c',
6 => 'ee',
10 => 't',
11 => 'x',
12 => 'w',
)
$data = array('a','b','c',1,2,3,4,5,'t','x','w');
$integerArray = array();
$stringArray = array();
$undefinedArray = array();
foreach($data as $temp)
{
if(gettype($temp) == "integer")
{
array_push($integerArray,$temp);
}elseif(gettype($temp) == "string"){
array_push($stringArray,$temp);
}else{
array_push($undefinedArray,$temp);
}
}

multidimensional array to one dimensional array recursively

I have this multidimensional array
$liste = [[1,2,3],5,[['x','y','z'],true]];
and I want to change it to one dimensionel array
$liste = [1,2,3,5,'x','y','z',true];
so i always have a problem that give me the same shape
function to_array($list){
$out=[];
if(!is_array($list)){
return $list;
}else{
foreach($list as $line){
$out[]= to_array($line);
}
}
return $out;
}
where is the problem in this recursive function !!!
The issue with your code is that you are pushing the result of to_array into $out, when what you want to do is use array_merge. Now since that requires both parameters to be arrays, when $list is not an array, you need to return an array containing the individual value. So change these lines:
return $list;
$out[]= to_array($line);
To:
return array($list);
$out = array_merge(to_array($line));
i.e.
function to_array($list){
$out=[];
if(!is_array($list)){
return array($list);
}else{
foreach($list as $line){
$out = array_merge($out, to_array($line));
}
}
return $out;
}
And you will get the result that you want:
var_export(to_array($liste));
Output:
array (
0 => 1,
1 => 2,
2 => 3,
3 => 5,
4 => 'x',
5 => 'y',
6 => 'z',
7 => true,
)
array_walk_recursive() delivers the desired result from an array of indeterminate depth in a one-liner because it only visits the "leaf-nodes" -- effectively, you don't need to bother checking if an element is or is not an array.
array_walk_recursive() doesn't return an array, it returns true|false based on whether or not there was a failure.
&$flat is a variable which is "passed by reference". This means that $flat can act as a vehicle to transport the data from inside the function scope to outside the function scope. As the elements are traversed, each new value is pushed into $flat using square bracket syntax.
This is exactly what this function does best -- use it.
Code: (Demo)
$liste = [[1, 2, 3], 5, [['x', 'y', 'z'], true]];
array_walk_recursive($liste, function($v) use (&$flat){ $flat[] = $v; });
var_export($flat);
Output:
array (
0 => 1,
1 => 2,
2 => 3,
3 => 5,
4 => 'x',
5 => 'y',
6 => 'z',
7 => true,
)

Shuffle the order of keys in an associative array, if they have the same values?

Given an associative array like this, how can you shuffle the order of keys that have the same value?
array(a => 1,
b => 2, // make b or c ordered first, randomly
c => 2,
d => 4,
e => 5, // make e or f ordered first, randomly
f => 5);
The approach I tried was to turn it into a structure like this and shuffle the values (which are arrays of the original keys) and then flatten it back into the original form. Is there a simpler or cleaner approach? (I'm not worried about efficiency, this is for small data sets.)
array(1 => [a],
2 => [b, c], // shuffle these
4 => [d],
5 => [e, f]); // shuffle these
function array_sort_randomize_equal_values($array) {
$collect_by_value = array();
foreach ($array as $key => $value) {
if (! array_key_exists($value, $collect_by_value)) {
$collect_by_value[$value] = array();
}
// note the &, we want to modify the array, not get a copy
$subarray = &$collect_by_value[$value];
array_push($subarray, $key);
}
arsort($collect_by_value);
$reordered = array();
foreach ($collect_by_value as $value => $array_of_keys) {
// after randomizing keys with the same value, create a new array
shuffle($array_of_keys);
foreach ($array_of_keys as $key) {
array_push($reordered, $value);
}
}
return $reordered;
}
I rewrote the entire code, since I found another way which is a lot simpler and faster than the old one(If you are still interested in the old one see the revision):
old code (100,000 executions): Ø 4.4 sec.
new code (100,000 executions): Ø 1.3 sec.
Explanation
First we get all unique values from the array with array_flip(), since then the values are the keys and you can't have duplicate keys in an array we have our unique values. We also create an array $result for then storing our result in it and $keyPool for storing all keys for each value.
Now we loop through our unique values and get all keys which have the same value into an array with array_keys() and save it in $keyPool with the value as key. We can also right away shuffle() the keys array, so that they are already random:
foreach($uniqueValues as $value => $notNeeded){
$keyPool[$value] = array_keys($arr, $value, TRUE);
shuffle($keyPool[$value]);
}
Now we can already loop through our original array and get a key with array_shift() from the $keyPool for each value and save it in $result:
foreach($arr as $value)
$result[array_shift($keyPool[$value])] = $value;
Since we already shuffled the array the keys already have a random order and we just use array_shift(), so that we can't use the key twice.
Code
<?php
$arr = ["a" => 1, "b" => 1, "c" => 1, "d" => 1, "e" => 1, "f" => 2,
"g" => 1, "h" => 3, "i" => 4, "j" => 5, "k" => 5];
function randomize_duplicate_array_value_keys(array $arr){
$uniqueValues = array_flip($arr);
$result = [];
$keyPool = [];
foreach($uniqueValues as $value => $notNeeded){
$keyPool[$value] = array_keys($arr, $value, TRUE);
shuffle($keyPool[$value]);
}
foreach($arr as $value)
$result[array_shift($keyPool[$value])] = $value;
return $result;
}
$result = randomize_duplicate_array_value_keys($arr);
print_r($result);
?>
(possible) output:
Array (
[b] => 1
[g] => 1
[a] => 1
[e] => 1
[d] => 1
[f] => 2
[c] => 1
[h] => 3
[i] => 4
[k] => 5
[j] => 5
)
Footnotes
I used array_flip() instead of array_unique() to get the unique values from the array, since it's slightly faster.
I also removed the if statement to check if the array has more than one elements and needs to be shuffled, since with and without the if statement the code runs pretty much with the same execution time. I just removed it to make it easier to understand and the code more readable:
if(count($keyPool[$value]) > 1)
shuffle($keyPool[$value]);
You can also make some optimization changes if you want:
Preemptively return, if you get an empty array, e.g.
function randomize_duplicate_array_value_keys(array $arr){
if(empty($arr))
return [];
$uniqueValues = array_flip($arr);
$result = [];
//***
}
Preemptively return the array, if it doesn't have duplicate values:
function randomize_duplicate_array_value_keys(array $arr){
if(empty($arr))
return [];
elseif(empty(array_filter(array_count_values($arr), function($v){return $v > 1;})))
return [];
$uniqueValues = array_flip($arr);
$result = [];
//***
}
Here's another way that iterates through the sorted array while keeping track of the previous value. If the previous value is different than the current one, then the previous value is added to a new array while the current value becomes the previous value. If the current value is the same as the previous value, then depending on the outcome of rand(0,1) either the previous value is added to the new list as before, or the current value is added to the new list first:
<?php
$l = ['a' => 1,'b' => 2, 'c' => 2,
'd' => 4,'e' => 5,'f' => 5];
asort($l);
$prevK = key($l);
$prevV = array_shift($l); //initialize prev to 1st element
$shuffled = [];
foreach($l as $k => $v) {
if($v != $prevV || rand(0,1)) {
$shuffled[$prevK] = $prevV;
$prevK = $k;
$prevV = $v;
}
else {
$shuffled[$k] = $v;
}
}
$shuffled[$prevK] = $prevV;
print_r($shuffled);

Find highest value in multidimensional array [duplicate]

This question already has answers here:
Get min and max value in PHP Array
(9 answers)
Closed 2 years ago.
The Problem
I have a multidimensional array similar to the one below. What I'm trying to achieve is a way to find and retrieve from the array the one with the highest "Total" value, now I know there's a function called max but that doesn't work with a multidimensional array like this.
What I've thought about doing is creating a foreach loop and building a new array with only the totals, then using max to find the max value, which would work, the only issue would then be retrieving the rest of the data which relates to that max value. I'm not sure that's the most efficient way either.
Any ideas?
Array
(
[0] => Array
(
[Key1] => Key1
[Total] => 13
)
[1] => Array
(
[Key2] => Key2
[Total] => 117
)
[2] => Array
(
[Key3] => Key3
[Total] => 39
)
)
Since PHP 5.5 you can use array_column to get an array of values for specific key, and max it.
max(array_column($array, 'Total'))
Just do a simple loop and compare values or use array_reduce. # is an error suppressor; it hides the fact that $a['total'] is not declared before it is accessed on the first iteration. Demo
$data = array_reduce($data, function ($a, $b) {
return #$a['Total'] > $b['Total'] ? $a : $b ;
});
print_r($data);
// Array( [Key2] => Key2 [Total] => 117 )
It could also be written with arrow function syntax which has been avaiable since PHP7.4. Demo
var_export(
array_reduce(
$data,
fn($result, $row) =>
$result['Total'] > $row['Total']
? $result
: $row,
['Key1' => null, 'Total' => PHP_INT_MIN]
)
);
// array ('Key2' => 'Key2', 'Total' => 117,)
It's so basic algorithm.
$max = -9999999; //will hold max val
$found_item = null; //will hold item with max val;
foreach($arr as $k=>$v)
{
if($v['Total']>$max)
{
$max = $v['Total'];
$found_item = $v;
}
}
echo "max value is $max";
print_r($found_item);
Working demo
I know this question is old, but I'm providing the following answer in response to another question that pointed here after being marked as a duplicate. This is another alternative I don't see mentioned in the current answers.
I know there's a function called max but that doesn't work with a multidimensional array like this.
You can get around that with array_column which makes getting the maximum value very easy:
$arr = [['message_id' => 1,
'points' => 3],
['message_id' => 2,
'points' => 2],
['message_id' => 3,
'points' => 2]];
// max value
$max = max(array_column($arr, 'points'));
Getting the associative key is where it gets a little more tricky, considering that you might actually want multiple keys (if $max matches more than one value). You can do this with an anonymous function inside array_map, and use array_filter to remove the null values:
// keys of max value
$keys = array_filter(array_map(function ($arr) use ($max) {
return $arr['points'] == $max ? $arr['message_id'] : null;
}, $arr));
Output:
array(1) {
[0]=>
int(1)
}
If you do end up with multiples keys but are only interested in the first match found, then simply reference $keys[0].
another simple method will be
$max = array_map( function( $arr ) {
global $last;
return (int)( ( $arr["Total"] > $last ) ? $arr["Total"] : $last );
}, $array );
print_r( max( $max ) );
<?php
$myarray = array(
0 => array(
'Key1' => 'Key1',
'Total' => 13,
),
1 => array(
'Key2' => 'Key2',
'Total' => 117,
),
2 => array(
'Key2' => 'Key3',
'Total' => 39,
),
);
$out = array();
foreach ($myarray as $item) {
$out[] = $item['Total'];
}
echo max($out); //117
unset($out, $item);
Can be done using array_walk(array_walk_recursive if needed)
$arr is the array you want to search in
$largestElement = null;
array_walk($arr, function(&$item, $key) use (&$largestElement) {
if (!is_array($largestElement) || $largestElement["Total"] < $item["Total"]) {
$largestElement = $item;
}
});
You can use php usort function:
http://php.net/manual/en/function.usort.php
A pretty illustrative example is given here:
<?php
function cmp($a, $b)
{
return strcmp($a["fruit"], $b["fruit"]);
}
$fruits[0]["fruit"] = "lemons";
$fruits[1]["fruit"] = "apples";
$fruits[2]["fruit"] = "grapes";
usort($fruits, "cmp");
while (list($key, $value) = each($fruits)) {
echo "\$fruits[$key]: " . $value["fruit"] . "\n";
}
?>
So it will sort the max value to the last array index.
Output:
$fruits[0]: apples
$fruits[1]: grapes
$fruits[2]: lemons
This example is given on aforementioned link
array_reduce accepts a 3rd "initial" parameter. Use this to avoid the bad practice of using "#" error suppression :
$data = array_reduce($data, function ($a, $b) {
return $a['Total'] > $b['Total'] ? $a : $b ;
},['Total' => 0]);
print_r($data);
PHP 7.4
$data = array_reduce($data, fn(a,b) => $a['Total'] > $b['Total'] ? $a : $b, ['Total' => 0]);

Merge two 2d arrays grouping on one column value and summing another column value within each group

Basically I need to take two arrays, merge them with unique values and sum one of columns. It makes more sense when written out below:
$a = [
['ID' => 1, 'Count' => 2],
];
$b = [
['ID' => 1, 'Count' => 4],
['ID' => 2, 'Count' => 3]
];
and I need the final product to be:
$a_plus_b = [
['ID' => 1, 'Count' => 6],
['ID' => 2, 'Count' => 3]
];
I have been playing with different variations of array_merge() and array_unique(), but I can't find an efficient way to do what I need. I know I can always do nested loops, but I was hoping for something easier. Any ideas?
This should do the trick
Note: This solution requires PHP >= 5.3. There is a PHP < 5.3 solution below.
$input = array($a, $b);
// add as many result arrays to $input as you want; e.g.,
// $input = array($a, $b, $c, $d);
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
function($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
},
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
Output
Array
(
[1] => 6
[2] => 3
)
Note the array keys above are ID values. The array values are Count values.
If you're running PHP < 5.2 you won't be able to use the inline closure with array_fill. You have to define it as a separate function.
$input = array($a, $b);
function _fill($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
}
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
'_fill',
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
From here, converting the output to your desired format is a trivial task.
Please don't over-engineer such a basic task. Iterate both array with a single loop and assign temporary keys using ID values. If encountering a respective ID key more than once, just add the new Count value to the stored value.
Code: (Demo)
$result = [];
foreach (array_merge($a, $b) as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'ID' => 1,
'Count' => 6,
),
1 =>
array (
'ID' => 2,
'Count' => 3,
),
)
Functional programming can be used as well to achieve the same result -- array_reduce() is ideal since the number of elements in the output will be equal to or less than the number of elements in the input data.
Code: (Demo)
var_export(
array_values(
array_reduce(
array_merge($a, $b),
function ($result, $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
return $result;
},
[]
)
)
);
If the ID values in the first array are guaranteed to be unique, you can avoid the array_merge() call by porting the $a array to the result array and assigning temporary keys using the ID values. (Demo)
$result = array_column($a, null, 'ID');
foreach ($b as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));

Categories