Related
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 PHP, is there an array function do the same thing as the code below? I'm wondering if it's possible with something like array_map or array_reduce in such a way that the $items array does not need to be declared beforehand.
$data = [
['order_product_id' => 123, 'quantity' => 1],
['order_product_id' => 456, 'quantity' => 2],
['order_product_id' => 567, 'quantity' => 3],
];
$items = [];
foreach ($data as $item) {
$items[$item->order_product_id] = ['quantity' => $item->quantity];
}
print_r($items);
/*
Array
(
[123] => ['quantity' => 1]
[456] => ['quantity' => 2]
[789] => ['quantity' => 3]
)
*/
You can use array_map to do that:
<?php
$data = [
['order_product_id' => 123, 'quantity' => 1],
['order_product_id' => 456, 'quantity' => 2],
['order_product_id' => 567, 'quantity' => 3],
];
$items = [];
array_map(function ($item) use (&$items) {
$items[$item['order_product_id']] = ['quantity' => $item['quantity']];
}, $data);
You can use two array_map calls to do this too:
$keys = array_map(function ($item) {
return $item['order_product_id'];
}, $data);
$values = array_map(function ($item) {
return ['quantity' => $item['quantity']];
}, $data);
$items = array_combine($keys, $values);
Or if you don't want to declare any temporary variables:
$items = array_combine(
array_map(function ($item) {
return $item['order_product_id'];
}, $data),
array_map(function ($item) {
return ['quantity' => $item['quantity']];
}, $data)
);
None of them are as efficient as foreach.
Array_reduce will do what you want as it hides the declaration of the output array in the 'initial value' of the 'carry' or 'accumulator' parameter. Also, rather than 'reduce' it actually adds entries to the output.
imo, It is more useful to think of the carry parameter to the callback as an accumulator. In this case, the 'accumulator' is an array, that we are adding entries to.
imo, It isn't as easy to understand as the original version. And is no faster.
$data = [
(object) ['order_product_id' => 123, 'quantity' => 1],
(object) ['order_product_id' => 456, 'quantity' => 2],
(object) ['order_product_id' => 567, 'quantity' => 3],
];
$items = array_reduce($data,
function ($out, $item) {
$out[$item->order_product_id] = ['quantity' => $item->quantity];
return $out;
},
array());
print_r($items);
Output:
Array(
[123] => Array([quantity] => 1)
[456] => Array([quantity] => 2)
[567] => Array([quantity] => 3)
)
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;
}, [])
This question already has answers here:
Is there a function to extract a 'column' from an array in PHP?
(15 answers)
Closed last month.
I'm sure this question has been asked before, my apologies for not finding it first.
The original array:
[0] => Array
(
[categoryId] => 1
[eventId] => 2
[eventName] => 3
[vendorName] => 4
)
[1] => Array
(
[categoryId] => 5
[eventId] => 6
[eventName] => 7
[vendorName] => 8
)
[2] => Array
(
[categoryId] => 9
[eventId] => 10
[eventName] => 11
[vendorName] => 12
)
My hoped for result out of: print_r(get_values_from_a_key_in_arrays('categoryId', $array));
[0] => 1
[1] => 5
[2] => 9
I'm just looking for something cleaner than writing my own foreach based function. If foreach is the answer, I already have that in place.
Edit: I don't want to use a hard-coded key, I was just showing an example call to the solution. Thanks! ^_^
Quick Grab Solution for PHP 5.3:
private function pluck($key, $data) {
return array_reduce($data, function($result, $array) use($key) {
isset($array[$key]) && $result[] = $array[$key];
return $result;
}, array());
}
So, the cool thing about higher-order collection/iterator functions such as pluck, filter, each, map, and friends is that they can be mixed and matched to compose a more complex set of operations.
Most languages provide these types of functions (look for packages like collection, iterator, or enumeration/enumerable)...some provide more functions than others and you will commonly see that the functions are named differently across languages (i.e. collect == map, reduce == fold). If a function doesn't exist in your language, you can create it from the ones that do exist.
As for your test case...we can use array_reduce to implement pluck. The first version I posted relied on array_map; however, I agree with #salathe that array_reduce is more succinct for this task; array_map is an OK option, but you end up having to do more work in the end. array_reduce can look a bit odd at first, but if the callback is neatly organized, all is well.
A less naive pluck would also check to see if it can "call" (a function/method) on the iterated value. In the naive implementation below, we assume the structure to be a hash (associative array).
This will setup the test-case data (Fixtures):
<?php
$data[] = array('categoryId' => 1, 'eventId' => 2, 'eventName' => 3, 'vendorName' => 4);
$data[] = array('categoryId' => 5, 'eventId' => 6, 'eventName' => 7, 'vendorName' => 8);
$data[] = array('categoryId' => 9, 'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
$data[] = array(/* no categoryId */ 'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
$data[] = array('categoryId' => false,'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
$data[] = array('categoryId' => 0.0, 'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
Choose the version of pluck you'd prefer
$preferredPluck = 'pluck_array_reduce'; // or pluck_array_map
"pluck" for PHP 5.3+: array_reduce provides a terse implementation though not as easy to reason about as the array_map version:
function pluck_array_reduce($key, $data) {
return array_reduce($data, function($result, $array) use($key){
isset($array[$key]) &&
$result[] = $array[$key];
return $result;
}, array());
}
"pluck" for PHP 5.3+: array_map isn't perfect for this so we have to do more checking (and it still doesn't account for many potential cases):
function pluck_array_map($key, $data) {
$map = array_map(function($array) use($key){
return isset($array[$key]) ? $array[$key] : null;
}, $data);
// is_scalar isn't perfect; to make this right for you, you may have to adjust
return array_filter($map, 'is_scalar');
}
"pluck" for legacy PHP <5.3
We could have used the legacy create_function; however, it is bad form, not recommended, and also not at all elegant, thus, I've decided not to show it.
function pluck_compat($key, $data) {
$map = array();
foreach ($data as $array) {
if (array_key_exists($key, $array)) {
$map[] = $array[$key];
}
}
unset($array);
return $map;
}
Here we choose a version of "pluck" to call based on the version of PHP we are running. If you run the entire script, you should get the correct answer no matter what version you are on.
$actual = version_compare(PHP_VERSION, '5.3.0', '>=')
? $preferredPluck('categoryId', $data)
: pluck_compat('categoryId', $data);
$expected = array(1, 5, 9, false, 0.0);
$variance = count(array_diff($expected, $actual));
var_dump($expected, $actual);
echo PHP_EOL;
echo 'variance: ', $variance, PHP_EOL;
print #assert($variance)
? 'Assertion Failed'
: 'Assertion Passed';
Notice there is no ending '?>'. That is because it isn't needed. More good can come of leaving it off than from keeping it around.
FWIW, it looks like this is being added to PHP 5.5 as array_column.
Mapping is what you need:
$input = array(
array(
'categoryId' => 1,
'eventId' => 2,
'eventName' => 3,
'vendorName' => 4,
),
array(
'categoryId' => 5,
'eventId' => 6,
'eventName' => 7,
'vendorName' => 8,
),
array(
'categoryId' => 9,
'eventId' => 10,
'eventName' => 11,
'vendorName' => 12,
),
);
$result = array_map(function($val){
return $val['categoryId'];
}, $input);
Or creating a function you wanted:
function get_values_from_a_key_in_arrays($key, $input){
return array_map(function($val) use ($key) {
return $val[$key];
}, $input);
};
and then using it:
$result = get_values_from_a_key_in_arrays('categoryId', $array);
It will work in PHP >= 5.3, where anonymous callbacks are allowed. For earlier versions you will need to define callback earlier and pass its name instead of anonymous function.
There's no built-in function for this, but it's usually referred as "pluck".
<?php
$a = array(
array('a' => 1, 'b' => 2),
array('a' => 2, 'b' => 2),
array('a' => 3, 'b' => 2),
array('a' => 4, 'b' => 2)
);
function get_a($v) {
return $v['a'];
}
var_dump(array_map('get_a', $a));
You can use an create_function or an anonymous function (PHP 5.3 >=)
<?php
$a = array(
array('a' => 1, 'b' => 2),
array('a' => 2, 'b' => 2),
array('a' => 3, 'b' => 2),
array('a' => 4, 'b' => 2)
);
var_dump(array_map(create_function('$v', 'return $v["a"];'), $a));
I'd write a callback function, as above, and then use it with array_map.
As of PHP 5.5, use array_column:
$events = [
[ 'categoryId' => 1, 'eventId' => 2, 'eventName' => 3, 'vendorName' => 4 ],
[ 'categoryId' => 5, 'eventId' => 6, 'eventName' => 7, 'vendorName' => 8 ],
[ 'categoryId' => 9, 'eventId' => 10, 'eventName' => 11, 'vendorName' => 12 ],
];
print_r(array_column($events, 'categoryId'));
See it online at 3v4l.
For versions before 5.5, you may consider using a polyfill.
There's no built in function. But one is easily made with array_map().
$array = array(
array(
"categoryID" => 1,
"CategoryName" => 2,
"EventName" => 3,
"VendorName" => 4
),
array(
"categoryID" => 5,
"CategoryName" => 6,
"EventName" => 7,
"VendorName" => 8
),
array(
"categoryID" => 9,
"CategoryName" => 10,
"EventName" => 11,
"VendorName" => 12
)
);
$newArray = array_map(function($el) {
return $el["categoryID"];
}, $array);
var_dump($newArray);
Where is lisp when you need it? Actually in php it is pretty easy to manage too. Just use the array_map function as illustrated below.
# bunch o data
$data = array();
$data[0] = array("id" => 100, "name" => 'ted');
$data[1] = array("id" => 200, "name" => 'mac');
$data[2] = array("id" => 204, "name" => 'bub');
# what you want to do to each bit of it
function pick($n) { return($n['id']); }
# what you get after you do that
$map = array_map("pick", $data);
# see for yourself
print_r($map);
You could use array_filter, and pass in a function based on the desired key.
Tadeck's answer is way better though.
I want to combine two arrays like this:
1st array:
array( "ATTENDED" => 1,
"TENTATIVE" => 2, //
"REJECTED" => 3,
"OUTSTANDING" => 4,
"ACCEPTED" => 6
);
2nd Array:
array ( 1 => 29,
4 => 30,
6 => 47
);
I want to get the results like this:
array ( 'ATTENDED' => 29,
'OUTSTANDING' => 30,
'ACCEPTED' => 47
);
2nd array is flexible. I can flip keys and values.
or better yet:
array( "ATTENDED" => 29,
"TENTATIVE" => 0, //
"REJECTED" => 0,
"OUTSTANDING" => 30,
"ACCEPTED" => 47
);
I know there must be a simple solution.
Any ideas?
foreach ($arr1 as $k1 => $v1) {
$arr1[$k1] = isset($arr2[$v1]) ? $arr2[$v1] : 0;
}
edit-
This is without an explicit loop, although I don't think this is really better, but maybe cooler though.
$mapped = array_map(function($valFromArr1) use ($arr2) {
return isset($arr2[$valFromArr1]) ? $arr2[$valFromArr1] : 0;
}, $arr1);
I can't think of a sane way to just use pure php functions.
$labels = array(
"ATTENDED" => 1,
"TENTATIVE" => 2,
"REJECTED" => 3,
"OUTSTANDING" => 4,
"ACCEPTED" => 6
);
$values = array(
1 => 29,
4 => 30,
6 => 47
);
$results = array();
foreach ($labels as $label => $id) {
$results[$label] = array_key_exists($id, $values) ? $values[$id] : 0;
}