I have two arrays one contains ids that's going to check if it exists on the second array which is an associative array:
Array 1: [1,2,11, 4]
Array 2:
[["id" => 1, "name" => "abc"], ["id" => 2, "name"=> "xyz"]]
Currently using a nested foreach to iterate over and match them but I wanted to know if there was anything more efficient as the arrays will be a lot larger.
$item = [1,2,11, 4];
$data = [["id" => 1, "name" => "abc"], ["id" => 2, "name"=> "xyz"]];
foreach($items as $item)
{
foreach($data as $key => $products)
{
foreach($products as $product)
{
if($product['id'] == $item)
{
echo $product['name'];
}
}
}
}
Had a look at this and this but none really fit my scenario.
Any help appreciated.
array_filter would be an option:
$ids = [ 1, 2, 11, 4 ];
$data = [ [ 'id' => 1, 'name' => 'abc' ], [ 'id' => 2, 'name' => 'xyz' ], [ 'id' => 3, 'name' => 'nono' ] ];
$result = array_filter($data, fn($value) => (in_array($value['id'], $ids)));
You can use array_column() to get all product ids as an indexed array.
Then you can use array_intersect() to fetch the id's that exists in both arrays.
$item = [1,2,11, 4];
$products = [["id" => 1, "name" => "abc"], ["id" => 2, "name"=> "xyz"]];
// Get the ids of all products as an indexed array
$prodIds = array_column($products, 'id');
// Check the id's that exists in both
$existingIds = array_intersect($item, $prodIds);
Demo: https://3v4l.org/HJSoq
Of if you rather do it as a one-liner:
$existingIds = array_intersect($item, array_column($products, 'id'));
You can also use the array_map and the anonymous function.
$item = [1, 2, 11, 4];
$products = [["id" => 1, "name" => "abc"], ["id" => 2, "name" => "xyz"]];
$result = array_map(function ($row) use ($item) {
return (in_array($row['id'], $item))?$row:false;
}, $products);
Result dump:
Array
(
[0] => Array
(
[id] => 1
[name] => abc
)
[1] => Array
(
[id] => 2
[name] => xyz
)
)
Related
Please, consider the following arrays:
$reference = array(
'080604' => 4,
'080703' => 4,
'080734' => 2,
'080819' => 2,
'088341' => 2,
'805238' => 20,
'805283' => 4,
'805290' => 2,
'805849' => 2,
'806051' => 2,
'806068' => 2,
);
$test = array(
'080604' => 2,
'080703' => 4,
'080819' => 1,
'088341' => 2,
'805238' => 20,
'805283' => 4,
'805290' => 2,
'805849' => 2,
'806051' => 2,
'806068' => 2,
);
They are quite similar, but can have some various differences, e.g. it's possible that:
- some keys of $reference are not present in $test at all
- some keys of $test are not present in $reference at all
- all keys are present, but the values in $reference and $test are different (sometimes $reference value is bigger than $test and sometimes the value of $test is bigger than $reference)
I need to find out the differences automatically and to output them in a way, that not only the difference in count itself, but also a description is provided, e.g.
$result = [
'080604' => [
'reference' => 4,
'test' => 2
]
];
If some value is in only one of the lists:
$result = [
'1234567890' => [
'reference' => 0,
'test' => 2
]
];
or something like that.
Does someone have an idea, which is the best way to accomplish this in an elegant way? Thank you very much!
Iterate over each and populate the array with values if present:
$combined = [];
foreach ($reference as $key => $val) {
$combined[$key] = [
'test' => 0,
'reference' => $val,
];
}
foreach ($test as $key => $val) {
if (!isset($combined[$key])) {
$combined[$key] = [
'reference' => 0,
'test' => 0,
]
}
$combined[$key]['test'] = $val;
}
$combined will contain both values from both arrays with reference to both the elements from $reference and $test.
try
$result = array_diff($reference, $test);
print_r($result)
I need to remove rows from my input array where duplicate values occur in a specific column.
Sample array:
$array = [
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5],
['user_id' => 76, 'ac_type' => 1],
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5]
];
I'd like to filter by user_id to ensure uniqueness and achieve this result:
So, my output will be like this:
[
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5],
['user_id' => 76, 'ac_type' => 1]
]
I've already tried with:
$result = array_unique($array, SORT_REGULAR);
and
$result = array_map("unserialize", array_unique(array_map("serialize", $array)));
and
$result = array();
foreach ($array as $k => $v) {
$results[implode($v)] = $v;
}
$results = array_values($results);
print_r($results);
but duplicate rows still exist.
For a clearer "minimal, complete, verifiable example", I'll use the following input array in my demos:
$array = [
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5],
['user_id' => 76, 'ac_type' => 1],
['user_id' => 82, 'ac_type' => 2],
['user_id' => 80, 'ac_type' => 5]
];
// elements [0] and [3] have the same user_id, but different ac_type
// elements [1] and [4] have identical row data
Unconditionally push rows into a result array and assign associative first-level keys, then re-index with array_values(). This approach overwrites earlier duplicate rows with later occurring ones.
array_column demo:
var_export(array_values(array_column($array, null, 'user_id')));
foreach demo:
$result = [];
foreach ($array as $row) {
$result[$row['user_id']] = $row;
}
var_export(array_values($result));
Output:
[
['user_id' => 82, 'ac_type' => 2], // was input row [3]
['user_id' => 80, 'ac_type' => 5], // was input row [4]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
]
Use a condition or the null coalescing assignment operator to preserve the first occurring row while removing duplicates.
foreach null coalescing assignment demo:
foreach ($array as $a) {
$result[$a['user_id']] ??= $a; // only store if first occurrence of user_id
}
var_export(array_values($result)); // re-index and print
foreach isset demo:
foreach ($array as $a) {
if (!isset($result[$a['user_id']])) {
$result[$a['user_id']] = $a; // only store if first occurrence of user_id
}
}
var_export(array_values($result)); // re-index and print
Output:
[
['user_id' => 82, 'ac_type' => 1], // was input row [0]
['user_id' => 80, 'ac_type' => 5], // was input row [1]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
]
It is also possible to unconditionally push data AND avoid a condition, but the row order may differ between the input and output (if it matters to you).
array_reverse, array_column demo:
var_export(array_values(array_column(array_reverse($array), null, 'user_id')));
array_reduce demo:
var_export(
array_values(
array_reduce(
$array,
fn($res, $row) => array_replace([$row['user_id'] => $row], $res),
[]
)
)
);
foreach array_reverse demo:
$result = [];
foreach (array_reverse($array) as $row) {
$result[$row['user_id']] = $row;
}
var_export(array_values($result));
Output:
[
['user_id' => 80, 'ac_type' => 5], // was input row [1]
['user_id' => 82, 'ac_type' => 1], // was input row [0]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
]
A warning about a fringe case not expressed in this example: if you are using row values as identifiers that may be corrupted upon being used as keys, the above techniques will give unreliable results. For instance, PHP does not allow float values as keys (they will cause an error or be truncated, depending on your PHP version). Only in these fringe cases might you consider using inefficient, iterated calls of in_array() to evaluate uniqueness.
Using array_unique(..., SORT_REGULAR) is only suitable when determining uniqueness by ENTIRE rows of data.
array_unique demo:
var_export(array_unique($array, SORT_REGULAR));
Output:
[
['user_id' => 82, 'ac_type' => 1], // was input row [0]
['user_id' => 80, 'ac_type' => 5], // was input row [1]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
['user_id' => 82, 'ac_type' => 2], // was input row [3]
]
As a slight extension of requirements, if uniqueness must be determined based on more than one column, but not all columns, then use a "composite key" composed of the meaningful column values. The following uses the null coalescing assignment operator, but the other techniques from #2 and #3 can also be implemented.
Code: (Demo)
foreach ($array as $row) {
$compositeKey = $row['user_id'] . '_' . $row['ac_type'];
$result[$compositeKey] ??= $row; // only store if first occurrence of compositeKey
}
$array = [
['user_id'=>82,'ac_type'=>1],
['user_id'=>80,'ac_type'=>5],
['user_id'=>76,'ac_type'=>1],
['user_id'=>82,'ac_type'=>2],
['user_id'=>80,'ac_type'=>6]
];
$array = array_reverse($array);
$v = array_reverse(
array_values(
array_combine(
array_column($array, 'user_id'),
$array
)
)
);
echo '<pre>';
var_dump($v);
Result:
array(3) {
[0]=>
array(2) {
["user_id"]=>
int(76)
["ac_type"]=>
int(1)
}
[1]=>
array(2) {
["user_id"]=>
int(82)
["ac_type"]=>
int(1)
}
[2]=>
array(2) {
["user_id"]=>
int(80)
["ac_type"]=>
int(5)
}
}
Took me a while, but this should work (explanation in comments):
<?php
/* Example array */
$result = array(
0 => array(
"user_id" => 82,
"ac_type" => 1
),
1 => array(
"user_id" => 80,
"ac_type" => 5
),
2 => array(
"user_id" => 76,
"ac_type" => 1
),
3 => array(
"user_id" => 82,
"ac_type" => 2
),
4 => array(
"user_id" => 80,
"ac_type" => 2
)
);
/* Function to get the keys of duplicate values */
function get_keys_for_duplicate_values($my_arr, $clean = false) {
if ($clean) {
return array_unique($my_arr);
}
$dups = $new_arr = array();
foreach ($my_arr as $key => $val) {
if (!isset($new_arr[$val])) {
$new_arr[$val] = $key;
} else {
if (isset($dups[$val])) {
$dups[$val][] = $key;
} else {
//$dups[$val] = array($key);
$dups[] = $key;
// Comment out the previous line, and uncomment the following line to
// include the initial key in the dups array.
// $dups[$val] = array($new_arr[$val], $key);
}
}
}
return $dups;
}
/* Create a new array with only the user_id values in it */
$userids = array_combine(array_keys($result), array_column($result, "user_id"));
/* Search for duplicate values in the newly created array and return their keys */
$dubs = get_keys_for_duplicate_values($userids);
/* Unset all the duplicate keys from the original array */
foreach($dubs as $key){
unset($result[$key]);
}
/* Re-arrange the original array keys */
$result = array_values($result);
echo '<pre>';
print_r($result);
echo '</pre>';
?>
Function was taken from this the answer to this question: Get the keys for duplicate values in an array
Output:
Array
(
[0] => Array
(
[user_id] => 82
[ac_type] => 1
)
[1] => Array
(
[user_id] => 80
[ac_type] => 5
)
[2] => Array
(
[user_id] => 76
[ac_type] => 1
)
)
Tested and working example.
<?php
$details = array('0'=> array('user_id'=>'82', 'ac_type'=>'1'), '1'=> array('user_id'=>'80', 'ac_type'=>'5'), '2'=>array('user_id'=>'76', 'ac_type'=>'1'), '3'=>array('user_id'=>'82', 'ac_type'=>'1'), '4'=>array('user_id'=>'80', 'ac_type'=>'5'));
function unique_multidim_array($array, $key) {
$temp_array = array();
$i = 0;
$key_array = array();
foreach($array as $val) {
if (!in_array($val[$key], $key_array)) {
$key_array[$i] = $val[$key];
$temp_array[$i] = $val;
}
$i++;
}
return $temp_array;
}
?>
<?php
$details = unique_multidim_array($details,'user_id');
?>
<pre>
<?php print_r($details); ?>
</pre>
Will output:
Array
(
[0] => Array
(
[user_id] => 82
[ac_type] => 1
)
[1] => Array
(
[user_id] => 80
[ac_type] => 5
)
[2] => Array
(
[user_id] => 76
[ac_type] => 1
)
)
taken from here http://php.net/manual/en/function.array-unique.php in the user contributed notes.
In PHP7, If I have this array:
$array = [
["name" => "Jon", "age" => 44],
["name" => "Bob", "age" => 32],
["name" => "Jim", "age" => 103],
["name" => "Ted", "age" => 19]
];
What is the most elegant way to process this array to add an extra column indicating who is the oldest to who is the youngest like so...
[
["name" => "Jon", "age" => 44, "order" => 2],
["name" => "Bob", "age" => 32, "order" => 3],
["name" => "Jim", "age" => 103, "order" => 1],
["name" => "Ted", "age" => 19, "order" => 4]
]
Here we are using multiple function to obtain desired output. array_column for extracting column age, the we are reverse sorting the column using arsort($columns); then getting array_values, then we are flipping array to get its order for age using array_flip, at last we are using array_map and array_merge to iterate and add a column in the array.
Try this code snippet here
<?php
ini_set('display_errors', 1);
$array = [
["name" => "Jon", "age" => 44],
["name" => "Bob", "age" => 32],
["name" => "Jim", "age" => 103],
["name" => "Ted", "age" => 19]
];
$columns= array_column($array, "age");//obtaining column age
arsort($columns);//sorting column in reverse
$column=array_flip(array_values($columns));//getting order for age
$result=array_map(function($value) use(&$column){
$value= array_merge($value,array("order"=>$column[$value["age"]]+1));//adding age column to array by adding index with 1
return $value;
}, $array);
print_r($result);
Output:
Array
(
[0] => Array
(
[name] => Jon
[age] => 44
[order] => 2
)
[1] => Array
(
[name] => Bob
[age] => 32
[order] => 3
)
[2] => Array
(
[name] => Jim
[age] => 103
[order] => 1
)
[3] => Array
(
[name] => Ted
[age] => 19
[order] => 4
)
)
Without a point of reference to determine which is the oldest user you're stuck either doing a multi loop comparison or a sort then insert.
A Multi loop comparasin would look something like
<?php
$array = [
["name" => "Jon", "age" => 44],
["name" => "Bob", "age" => 32],
["name" => "Jim", "age" => 103],
["name" => "Ted", "age" => 19]
];
$count = count($array);
for($index = 0; $index < $count; ++$index) {
$order = 1;
$age = $array[$index]["age"];
for($index2 = 0; $index2 < $count; ++$index2) {
if($array[$index2]["age"] > $age) {
++$order;
}
}
$array[$index]["order"] = $order;
}
echo "<pre>";
var_dump($array);
echo "</pre>";
Sort then insert would involve array_walk and uasort
From the docs
<?php
$fruits = array("d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple");
function test_alter(&$item1, $key, $prefix)
{
$item1 = "$prefix: $item1";
}
function test_print($item2, $key)
{
echo "$key. $item2<br />\n";
}
echo "Before ...:\n";
array_walk($fruits, 'test_print');
array_walk($fruits, 'test_alter', 'fruit');
echo "... and after:\n";
array_walk($fruits, 'test_print');
and docs
<?php
// Comparison function
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
// Array to be sorted
$array = array('a' => 4, 'b' => 8, 'c' => -1, 'd' => -9, 'e' => 2, 'f' => 5, 'g' => 3, 'h' => -4);
print_r($array);
// Sort and print the resulting array
uasort($array, 'cmp');
print_r($array);
so for your example it would be something like this:
<?php
function walkFunc(&$item, $key) {
$item["order"] = $key + 1;
}
// as #waterloomatt mentioned the Spaceship operator is meant for this
function sortComp($a, $b) {
return ($a["age"] <=> $b["age"]) * -1;
}
$array = [
["name" => "Jon", "age" => 44],
["name" => "Bob", "age" => 32],
["name" => "Jim", "age" => 103],
["name" => "Ted", "age" => 19]
];
uasort($array, 'sortComp');
array_walk($array, 'walkFunc');
echo "<pre>";
var_dump($array);
echo "</pre>";
Not going to lie, I just wanted to use the new spaceship operator. This does not add a new column but will instead sort the array descending by age.
<?php
$array = [
["name" => "Jon", "age" => 44],
["name" => "Bob", "age" => 32],
["name" => "Jim", "age" => 103],
["name" => "Ted", "age" => 19]
];
usort($array, function ($a, $b) {
return ($a["age"] <=> $b["age"]) * -1;
});
print_r($array);
To assign dense ranks to each row based on the age values, avoid using nested loops and convoluted piles of array functions. These techniques will have poor time complexity (efficiency) and/or will be very hard to maintain.
Instead, enjoy the sweet convenience of reference variables. Loop the input array just once and assign references to the new, desired order element in each row. To see what this does, view this -- these null values will be replaced by the calculated order.
Next, sort the keys of the ref array in a descending order.
Finally, iterate the sorted array and assign incremented values by reference. Because the ref array elements are "linked" to the $array rows, the placeholder elements instantly receive the new values.
The first loop is "linear" -- meaning that that it doesn't need to iterate the array more than once. The second loop may do fewer iterations than the first if duplicate ages are encountered.
Code: (Demo)
foreach ($array as &$row) {
$row['order'] = &$ref[$row['age']];
}
krsort($ref);
$order = 0;
foreach ($ref as &$v) {
$v = ++$order;
}
var_export($array);
Let's say I have one array of ID numbers in a desired particular order. I have a second array of objects that were sorted by an ID property from the first array.
$array1 = [3,4,2,1,5];
$array2 = [
["id" => 1, "data" => "one"],
["id" => 2, "data" => "two"],
["id" => 3, "data" => "fre"],
["id" => 4, "data" => "foe"],
["id" => 5, "data" => "fie"]
];
In PHP, what is the best way of 'unsorting' or reverting the second array to the original order of the first array?
The closest answer I can find without using a sort is:
$array1_flipped = array_flip($array1);
$array2_unsorted = array();
for($i = 0; $i < count($array1); $i++) {
$array2_unsorted[$array1_flipped[$array2[$i]['id']]] = $array2[$i];
}
return($array2_unsorted);
Edit: For those interested, here is how the question arose. The first array is a list of IDs to be displayed in a particular order. The second array is the return of the MySQL call WHERE id IN $array2, which is returned sorted. However, the second array needs to be resorted back into the order of the first array. Due to size issues, I was hoping to be able to remap the second array using the keys and values of the first array without sorting.
I found the solution by introducing a third array and using a method similar to Gauss-Jordan elimination. While this is beautiful, I wish there was a one-step algorithm for this. I'll award the correct answer to anyone who finds it.
$array1 = [3,4,2,1,5];
$array2 = [
["id" => 1, "data" => "one"],
["id" => 2, "data" => "two"],
["id" => 3, "data" => "fre"],
["id" => 4, "data" => "foe"],
["id" => 5, "data" => "fie"]
];
// Placeholder sorted ascending array (e.g. $array3 = [1,2,3,4,5])
$array3 = range(1,count($array1));
array_multisort($array1, $array3);
// Now $array3 = [4,3,1,2,5], the inverse map of an $array1 sort
array_multisort($array3, $array2);
return $array2;
usort with an anonymous function receiving the order array via the use keyword:
$order = [3,4,2,1,5];
$ar = [
["id" => 1, "data" => "one"],
["id" => 2, "data" => "two"],
["id" => 3, "data" => "fre"],
["id" => 4, "data" => "foe"],
["id" => 5, "data" => "fie"]
];
usort($ar, function($a, $b) use ($order) {
$ai = array_search($a['id'], $order);
$bi = array_search($b['id'], $order);
if($ai == $bi) return 0;
return ($ai < $bi) ? -1 : 1;
});
print_r($ar);
I have this initial array:
[
0 => ['id' => 5, 'value' => 50],
1 => ['id' => 6, 'value' => 60],
2 => ['id' => 7, 'value' => 70],
]
and want to convert it to:
[
5 => ['value' => 50],
6 => ['value' => 60],
7 => ['value' => 70],
]
At first, I tried to use map, but it can't modify the array keys, so I thought reduce would solve the problem because it reduces the array to a single value, in this case, an array. So I tried:
array_reduce(
$array,
function($carry, $item) {
return $carry[$item['id']] = $item['value'];
},
[]
);
But it returns this error Cannot use a scalar value as an array. What am I doing wrong? Does array_reduce cannot receive an array as an initial value?
Your array_reduce didn't work because You weren't returning the accumulator array (carry in Your case) from the callback function.
array_reduce(
$array,
function($carry, $item) {
$carry[$item['id']] = $item['value'];
return $carry; // this is the only line I added :)
},
[]
);
I came to this question while looking for a way to use array_reduce, so I felt I should write this comment. I hope this will help future readers. :)
As Mark Bakerdid it. I also did with foreach loop.
$arr = array(
array('id' => 5, 'value' => 50),
array('id' => 6, 'value' => 60),
array('id' => 7, 'value' => 70)
);
$result = array();
$result = array_column($arr, 'value', 'id');
array_walk($result, function(&$value) { $value = ['value' => $value]; });
//I did this using foreach loop, But the OP need it through array function.
//foreach($arr as $key => $value){
// $result[$value['id']] = array('value' => $value['value']);
//}
echo '<pre>';
print_r($result);
Result:
Array
(
[5] => Array
(
[value] => 50
)
[6] => Array
(
[value] => 60
)
[7] => Array
(
[value] => 70
)
)
Sometimes the best solutions are the simplest. Loop through your array and assign the id and value to a new array.
$new_array = array();
foreach ($array as $key => $arr) {
$new_array[$arr['id']] = array('value' => $arr['value']);
}
You can do it functionally. I suspect it's not actually more readable however.
array_combine(
array_column($a, 'id'),
array_map(function($v) { return ['value' => $v['value']]; }, $a)
);
Or even...
array_map(
function($v) { return ['value' => $v['value']]; },
array_column($a, null, 'id')
)
array_reduce($ar, function ($acc, $item) {
$acc[$item['id']] = [ 'value' => $item['value']];
return $acc;
}, [])