This question already has answers here:
Elegant way to search an PHP array using a user-defined function
(7 answers)
Closed 4 years ago.
I am looking for an elegant way to get the first (and only the first) element of an array that satisfies a given condition.
Simple example:
Input:
[
['value' => 100, 'tag' => 'a'],
['value' => 200, 'tag' => 'b'],
['value' => 300, 'tag' => 'a'],
]
Condition: $element['value'] > 199
Expected output:
['value' => 200, 'tag' => 'b']
I came up with several solutions myself:
Iterate over the array, check for the condition and break when found
Use array_filter to apply condition and take first value of filtered:
array_values(
array_filter(
$input,
function($e){
return $e['value'] >= 200;
}
)
)[0];
Both seems a little cumbersome. Does anyone have a cleaner solution? Am i missing a built-in php function?
The shortest I could find is using current:
current(array_filter($input, function($e) {...}));
current essentially gets the first element, or returns false if its empty.
If the code is being repeated often, it is probably best to extract it to its own function.
There's no need to use all above mentioned functions like array_filter. Because array_filter filters array. And filtering is not the same as find first value. So, just do this:
foreach ($array as $key => $value) {
if (meetsCondition($value)) {
$result = $value;
break;
// or: return $value; if in function
}
}
array_filter will filter whole array. So if your required value is first, and array has 100 or more elements, array_filter will still check all these elements. So, do you really need 100 iterations instead of 1? The asnwer is clear - no.
Your array :
$array = [
['value' => 100, 'tag' => 'a'],
['value' => 200, 'tag' => 'b'],
['value' => 300, 'tag' => 'a'],
];
To find the entries via conditions, you could do this
$newArray = array_values(array_filter($array, function($n){ return $n['value'] >= 101 && $n['value'] <= 400; }));
With this you can set to values, min and max numbers.
if you only want to set a min number, you can omit the max like this
$arrayByOnlyMin = array_values(array_filter($array, function($n){ return $n['value'] >= 199; }));
This would return :
array(2) {
[0]=>
array(2) {
["value"]=>
int(200)
["tag"]=>
string(1) "b"
}
[1]=>
array(2) {
["value"]=>
int(300)
["tag"]=>
string(1) "a"
}
}
so calling $arrayByOnlyMin[0] would give you the first entry, that matches your min condition.
Related
I have multidimensional array like this:
$obj = array(
"a" => array(
"aa" => array(
"aaa" => 1
),
"bb" => 2,
),
"b" => array(
"ba" => 3,
"bb" => 4,
),
"c" => array(
"ca" => 5,
"cb" => 6,
),
);
I can not figured out a neatest way, e.g. custom-depth function, to extract item at specific location with arguments to function (or array of key names). For example:
echo $obj[someFunc("a", "aa", "aaa")];
... should return 1.
print_r($obj[someFunc("a")]);
... should return:
Array
(
[aa] => Array
(
[aaa] => 1
)
[bb] => 2
)
What is the best way to accomplished this with php7 features?
Since PHP 5.6, ["Variadic functions"][1] have existed. These provide us with a nice simple to read way to collect arguments used in calling a function into a single array. For example:
function getValue(...$parts) {
var_dump($parts);
}
getValue('test', 'part');
Will output:
array(2) {
[0]=>
string(4) "test"
[1]=>
string(4) "part"
}
This was all possible before using built-in functions to get the parameters, but this is more readable.
You could also be a little more explicit with the argument types, but I'll leave that for you to figure out if necessary.
You next major challenge is to loop through the arguments. Something like this will produce the value that you desire.
function getValue(...$parts) {
$returnValue = $obj;
foreach ($parts as $part) {
$returnValue = $obj[$part];
}
return $returnValue;
}
However, this is rather crude code and will error when you try calling it to access non-existent parts. Have a play and fix those bits.
[1]: https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list
With PHP 7.4, I have associative array with objets :
$array = [
1 => Class {
'id' => 1,
'call' => true,
},
5 => Class {
'id' => 5,
'call' => false,
},
7 => Class {
'id' => 7,
'call' => true,
},
]
I want to extract all the IDs if the call attribute === true.
After searching, I think I should use the array_map function.
$ids = array_map(function($e) {
if (true === $e->call) {
return $e->id;
}
}, $array);
// Return :
array(3) {
[1]=> 1
[5]=> NULL
[7]=> 7
}
But I have two problems with this:
I don't want NULL results
I don't wish to have the associative keys in in my new array. I want a new array ([0=>1, 1=>7])
I know I can do it with a foreach() but I find it interesting to do it with a single PHP function (array_walk or array_map ?)
Create an array which is the call value indexed by the id and then filter it. As the id is the index, then extract the keys for the ids...
$ids = array_keys(array_filter(array_column($array, "call", "id")));
print_r($ids));
Array
(
[0] => 1
[1] => 7
)
Although I think a foreach() is a much more straight forward method.
You probably want array_filter to filter out what you don't want, then you can extract the id:
$ids = array_column(array_filter($array, function($e) {
return $e->call === true;
}), 'id');
You can reduce the array with a callback that conditionally adds each item's id to the "carry" array.
$ids = array_reduce($array, fn($c, $i) => $i->call ? [...$c, $i->id] : $c, []);
$array1 = [
'1' => '11',
'b' => 1,
3 => 33,
8 => 8
];
$array2 = [
'1' => '22',
'b' => 2,
3 => 44,
9 => 9
];
$merged = array_merge_recursive($array1, $array2);
and the result is:
array(7) {
[0]=>
string(2) "11"
["b"]=>
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
[1]=>
int(33)
[2]=>
int(8)
[3]=>
string(2) "22"
[4]=>
int(44)
[5]=>
int(9)
}
so lets take a glance: the only part is the 'b' keys, they are actually works. I dont want to overwrite anything of it but putting them together to an array. Thats good! But then keys the other numeric keys (int or string) are screwed up.
I want to have this as result:
[
'1' => ['11', '22']
'b' => [1, 2]
[3] => [33, 44]
[8] => 8,
[9] => 9
]
possible?
EDIT: of course keys "1" and 1 - string vs int key are the same
Let's break down this question into to separate problems:
When a key in the second array exist in the first array, you want to create an array and make the value the first element of that array.
To be honest, I don't know an easy way of solving this. I'm not sure there is one. And even if, I'm not sure you really want it. Such a function will lead to arrays having values that are a string or an array. How would you handle such an array?
Update: Well, there is one. Your example already shows that array_merge_recursive will convert values with a string key into an array. So 1 => 'foo' would be 0 => 'foo', but 'foo' => 'bar' will end up as 'foo' => ['bar']. I really don't understand this behaviour.
Using string keys would help you out in this case, but after learning more about array_merge_recursive I decided to avoid this function when possible. After I asked this question someone filed it as a bug in it since PHP 7.0, since it works differently in PHP 5.x.
You want to keep the keys, while array_merge_resursive doesn't preserve integer keys, while it does for integer keys:
If the input arrays have the same string keys, then the values for
these keys are merged together into an array, and this is done
recursively, so that if one of the values is an array itself, the
function will merge it with a corresponding entry in another array
too. If, however, the arrays have the same numeric key, the later
value will not overwrite the original value, but will be appended.
To make it worse, it handles differently when handling the nested arrays:
$array1 = [30 => [500 => 'foo', 600 => 'bar']];
$array2 = [];
array_merge_recursive($array1, $array2);
//[0 => [500=> 'foo', 600 => 'bar']];
So effectively, array_merge_resursive isn't really resursive.
Using array_replace_resursive solves that problem:
array_replace_recursive($array1, $array2);
//output:
//array:5 [
// 1 => "22"
// "b" => 2
// 3 => 44
// 8 => 8
// 9 => 9
//]
Please note that PHP is very consistent in being inconsistent. While array_merge_recursive isn't recursive, array_replace_recursive doesn't replace (it also appends):
If the key exists in the second array, and not the first, it will be
created in the first array.
In many cases this is desired behavior and since naming functions isn't PHP's strongest point, you can consider this as a minor issue.
Can you rely on a native function to return your exact desired output? No. At least not in any version as of the date of this post (upto PHP8.1).
The good news is that crafting your own solution is very simple.
Code: (Demo)
foreach ($array2 as $key => $value) {
if (!key_exists($key, $array1)) {
$array1[$key] = $value;
} else {
$array1[$key] = (array)$array1[$key];
$array1[$key][] = $value;
}
}
var_export($array1);
I suppose I am less inclined to recommend this output structure because you have potentially different datatypes on a given level. If you were building subsequent code to iterate this data, you'd need to write conditions on every level to see if the data was iterable -- it just feels like you are setting yourself up for code bloat/convolution. I'd prefer a result which has consistent depth and datatypes.
I am trying to sort an array of countries so that the row containing 'United States' (id 1) appears first, while every other country is sorted by alphabetical order.
How do I sort all EXCEPT United States to remain on top using the usort() function for an array?
Any alternative suggestions are also welcome. Here is my current code:
while (list($key, $value) = each($countries->countries)) {
$countries_array[] = array('id' => $key, 'text' => $value['countryname']);
}
function text_cmp($a, $b) {
return strcmp($a["text"], $b["text"]);
}
usort($countries_array, 'text_cmp');
The easiest way to do this is to remove the single value, sort and then re-add it:
working example
// your countries array goes here:
$countries = array(2=>"Burma", 4=>"Zimbabwe", 10=>"France", 1=>"United States", 13=>"Russia");
$AmericaFKYeah = array(1=>"United States");
$countries =array_diff($countries, $AmericaFKYeah);
asort($countries);
// using + instead of array_unshift() preserves numerical keys!
$countries= $AmericaFKYeah + $countries;
gives you:
array(5) {
[1]=>
string(13) "United States"
[2]=>
string(5) "Burma"
[10]=>
string(6) "France"
[13]=>
string(6) "Russia"
[4]=>
string(8) "Zimbabwe"
}
With Indexed Array
Steps
Find resource in array
Unset variable in array
Add resource as the first element in array with array_unshift (array_unshift — Prepend one or more elements to the beginning of an array - http://php.net/manual/en/function.array-unshift.php)
Code
if (in_array('yourResource', $a_array)){
unset($a_array[array_search('yourResource',$a_array)]);
array_unshift($a_array, 'yourResource');
}
With MultiDimensional Arrays
$yourResorceToFind = 'yourResource';
foreach ($a_arrays as $a_sub_array){
if (in_array($yourResorceToFind, $a_sub_array)){
unset($a_arrays[array_search($yourResorceToFind,$a_sub_array)]);
array_unshift($a_arrays, $a_sub_array);
break;
}
}
I found this topic trying to do the same thing in a ugly way but since my technique seems simpler I share it if someone needs.
The easiest way of doing it is by fiddling with the result of your usort callback. You just have to pass the value you want first in your callback like this :
$countries = [
['id' => 2, 'text' => 'Burma'],
['id' => 4, 'text' => 'Zimbabwe'],
['id' => 10, 'text' => 'France'],
['id' => 1, 'text' => 'United States'],
['id' => 13, 'text' => 'Russia'],
];
$myFirstItem = "United States";
usort($countries, function($a, $b) use ($myFirstItem) {
return $myFirstItem == $a['text'] ? -1 : ($myFirstItem == $b['text'] ? 1 : strcmp($a['text'], $b['text']));
});
It's kind of ugly but does the job without the need to play with other arrays and everything is done in the callback, as expected.
Generally speaking, you could re-key your array, then sort by keys, then merge the re-keyed array with an array with a lone US row, then re-index the array. However, I don't recommend it because the number of iterations required in the process is unattractively high. In other words, the time complexity is suboptimal.
$keyed = array_column($countries, null, 'text');
ksort($keyed);
var_export(array_values(['United States' => $keyed['United States']] + $keyed));
You could also call array_search() with array_column() isolating the text values, but are going to need to sort the column alphabetically anyhow. So again, I'd rule that out for efficiency reasons. I would also not recommend any approach using in_array().
The truth is that this task can be accomplished with a single usort() call.
Code: (Demo)
usort(
$countries,
fn($a, $b) =>
[$a['text'] !== 'United States', $a['text']]
<=>
[$b['text'] !== 'United States', $b['text']]
);
var_export($countries);
By providing $a data on the left side of the spaceship operator and $b data on the right, ASC sorting is down.
Ascending sorting of boolean values puts false before true. To ensure that the US row always has a higher priority than any other row, check if the text value is NOT United States.
The first element on each side of the spaceship operator is the first rule. When both text values are not United States, then standard alphabetical sorting is applied.
This is a highly efficient approach because there are no iterated function calls.
Finally, because you are generating a new, specific array structure from the countries property of your $countries object inside of a loop, you could make better use of that loop. Maybe something like this:
$result = [];
foreach ($countries->countries as $id => ['countryname' => $text]) {
$row = compact(['id' 'text']);
if ($text === 'United States') {
$result[] = $row;
} else {
$theRest[] = $row;
$texts[] = $text;
}
}
array_multisort($texts, $theRest);
array_push($result, ...$theRest);
var_export($result);
I have an PHP array that looks something like this:
Index Key Value
[0] 1 Awaiting for Confirmation
[1] 2 Assigned
[2] 3 In Progress
[3] 4 Completed
[4] 5 Mark As Spam
When I var_dump the array values i get this:
array(5) { [0]=> array(2) { ["key"]=> string(1) "1" ["value"]=> string(25) "Awaiting for Confirmation" } [1]=> array(2) { ["key"]=> string(1) "2" ["value"]=> string(9) "Assigned" } [2]=> array(2) { ["key"]=> string(1) "3" ["value"]=> string(11) "In Progress" } [3]=> array(2) { ["key"]=> string(1) "4" ["value"]=> string(9) "Completed" } [4]=> array(2) { ["key"]=> string(1) "5" ["value"]=> string(12) "Mark As Spam" } }
I wanted to remove Completed and Mark As Spam. I know I can unset[$array[3],$array[4]), but the problem is that sometimes the index number can be different.
Is there a way to remove them by matching the value name instead of the key value?
Your array is quite strange : why not just use the key as index, and the value as... the value ?
Wouldn't it be a lot easier if your array was declared like this :
$array = array(
1 => 'Awaiting for Confirmation',
2 => 'Asssigned',
3 => 'In Progress',
4 => 'Completed',
5 => 'Mark As Spam',
);
That would allow you to use your values of key as indexes to access the array...
And you'd be able to use functions to search on the values, such as array_search() :
$indexCompleted = array_search('Completed', $array);
unset($array[$indexCompleted]);
$indexSpam = array_search('Mark As Spam', $array);
unset($array[$indexSpam]);
var_dump($array);
Easier than with your array, no ?
Instead, with your array that looks like this :
$array = array(
array('key' => 1, 'value' => 'Awaiting for Confirmation'),
array('key' => 2, 'value' => 'Asssigned'),
array('key' => 3, 'value' => 'In Progress'),
array('key' => 4, 'value' => 'Completed'),
array('key' => 5, 'value' => 'Mark As Spam'),
);
You'll have to loop over all items, to analyse the value, and unset the right items :
foreach ($array as $index => $data) {
if ($data['value'] == 'Completed' || $data['value'] == 'Mark As Spam') {
unset($array[$index]);
}
}
var_dump($array);
Even if do-able, it's not that simple... and I insist : can you not change the format of your array, to work with a simpler key/value system ?
...
$array = array(
1 => 'Awaiting for Confirmation',
2 => 'Asssigned',
3 => 'In Progress',
4 => 'Completed',
5 => 'Mark As Spam',
);
return array_values($array);
...
$key = array_search("Mark As Spam", $array);
unset($array[$key]);
For 2D arrays...
$remove = array("Mark As Spam", "Completed");
foreach($arrays as $array){
foreach($array as $key => $value){
if(in_array($value, $remove)) unset($array[$key]);
}
}
You can use this
unset($dataArray['key']);
Why do not use array_diff?
$array = array(
1 => 'Awaiting for Confirmation',
2 => 'Asssigned',
3 => 'In Progress',
4 => 'Completed',
5 => 'Mark As Spam',
);
$to_delete = array('Completed', 'Mark As Spam');
$array = array_diff($array, $to_delete);
Just note that your array would be reindexed.
Try this:
$keys = array_keys($array, "Completed");
/edit
As mentioned by JohnP, this method only works for non-nested arrays.
I kinda disagree with the accepted answer. Sometimes an application architecture doesn't want you to mess with the array id, or makes it inconvenient. For instance, I use CakePHP quite a lot, and a database query returns the primary key as a value in each record, very similar to the above.
Assuming the array is not stupidly large, I would use array_filter. This will create a copy of the array, minus the records you want to remove, which you can assign back to the original array variable.
Although this may seem inefficient it's actually very much in vogue these days to have variables be immutable, and the fact that most php array functions return a new array rather than futzing with the original implies that PHP kinda wants you to do this too. And the more you work with arrays, and realize how difficult and annoying the unset() function is, this approach makes a lot of sense.
Anyway:
$my_array = array_filter($my_array,
function($el) {
return $el["value"]!="Completed" && $el!["value"]!="Marked as Spam";
});
You can use whatever inclusion logic (eg. your id field) in the embedded function that you want.
The way to do this to take your nested target array and copy it in single step to a non-nested array.
Delete the key(s) and then assign the final trimmed array to the nested node of the earlier array.
Here is a code to make it simple:
$temp_array = $list['resultset'][0];
unset($temp_array['badkey1']);
unset($temp_array['badkey2']);
$list['resultset'][0] = $temp_array;
for single array Item use reset($item)