For this nested array:
$status = array(
"house" => "OK",
"car" => array(
"car1" => "OK",
"car2" => "ERROR"
),
"boat" => "OK"
);
I want to know if a certain value "ERROR" exists at least once in the array. I don't care what key it's associated with, I just want to know if the $status array contains an "ERROR" status anywhere in it.
Is there a cleaner way to do this than just iterating over the elements with nested for loops?
You could use the function array_walk_recursive() to get all the values with any level of nested array.
https://secure.php.net/manual/en/function.array-walk-recursive.php
<?php
$status = array(
"house" => "OK",
"car" => array(
"car1" => "OK",
"car2" => "OK"
),
"boat" => "OK"
);
$required = array();
array_walk_recursive($status, function ($value, $key) use (&$required){
$required[] = $value;
}, $required);
print '<pre>';
print_r($required);
print '</pre>';
?>
Output:
Array
(
[0] => OK
[1] => OK
[2] => OK
[3] => OK
)
I actually wouldn't use array recursive. It falls short in a few key things you want to do. And while at first it may seem like a "shortcut" to what you want,if fails in a few key ways:
you can't return a simple testable value like a boolean true of false from the callback in it.
you can't build the $all array with nesting, because the keys that have an array as their value are never passed to the callback. This essentially flattens the array out.
it's not portable, or in other words it's not reusable as it's procedural in nature.
That said, honestly I wouldn't build the all array because it's pointless as you already have that array, so there is no point copying it. As I said I don't think you really want to rebuild $all but if you did you couldn't do it with array_walk_recursive even if you wanted to...
Anyway.
$array = ['zero','one', 'two' => ['zoo' => 'foo']];
function checkRecursive($value, $array){
$x = false;
foreach($array as $v){
if(is_array($v)) $x = checkRecursive($value, $v); //recursive
if($v == $value || $x == true) return true;
}
return false;
}
echo checkRecursive('one', $array) ? "true\n" : "false\n";
echo checkRecursive('foo', $array) ? "true\n" : "false\n";
echo checkRecursive('bar', $array) ? "true\n" : "false\n";
prints
true
true
false
Test it
http://sandbox.onlinephpfunctions.com/code/78d0de421475b8e3104376c698027107e9b7e948
UPDATE
I see you changed the question and while you say "value" your array says the "key"
$status = array(
"house" => "OK",
"car" => array(
"car1" => "OK",
"car2" => "OK"
),
"boat" => "OK"
);
Because all the values are the same. So if you really want to find the key instead then just change it to this
function checkRecursive($value, $array){
$x = false;
foreach($array as $k=>$v){
if(is_array($v)) $x = checkRecursive($value, $v); //recursive
if((string)$value == (string)$k || $x == true) return true;
}
return false;
}
A few things to note when checking for the keys, it's possible they will not always be strings, or that you will check using an unqoted number. In these cases PHP sometimes has a weird way of changing a string into an integer and non-numeric strings will come out as 0. For example "1" = 1 but "one" = 0 when taken as an integer. Therefore if you have an array with a key of 0 and you check for one it will match it because it will turn "one" into a 0.
You can solve this by casting everything to strings. Or by using the === strict type checking. I prefer the string method because checking with === would make 0 != "0" so if you tried finding a key of 0 with "0" it would say no match. This is true of other numbers. That is unless you plan on checking objects with it.
This is still an issue when looking for the "values" but it's less of one, because there you are not dealing with the native integer keys in an array, so there is a greater possibility you wouldn't encounter it.
It all has to do with PHP's loosely typed nature, it's a feature not a bug...
I put this small example togather:
echo '(int)"one": ';
echo (int)"one";
echo "\n";
echo "0 == 'one': " . (0 == "one" ? "true\n" : "false\n");
echo "1 == '1': " . (1 == "1" ? "true\n" : "false\n");
Outputs:
(int)"one": 0
0 == 'one': true
1 == '1': true
http://sandbox.onlinephpfunctions.com/code/df41f55251570a12d9ca693712bcdc785ad62f85
Related
I have a fairly easy issue where I need to see if an associative array of arrays is empty in php. My array looks like this:
array (
'person1' =>
array (
),
'person2' =>
array (
),
'person3' =>
array (
),
)
In my case, the three array's for the three people holds nothing so I need a test whether this is empty. I have done this which works:
if ( empty($form_values['person1']) && empty($form_values['person2']) && empty($form_values['person3'] ) ){
echo 'values empty!!';
}
But I was hoping something a bit more cleaner with using empty like the following:
if (empty( $form_values )) {
echo 'HI!';
}
You can use array_filter() to filter all of the empty array elements. You can then use empty to check if the result is empty then.
I've shorthanded the arrays so it's a easier to read since the arrays are empty. array() will work the same.
$form_values = [
'person1' => [],
'person2' => [],
'person3' => []
];
if (empty(array_filter($form_values))) {
// empty
} else {
// not empty
}
If you're looking for a one-liner then you could do something like:
$form_values = array (
'person1' =>
array (
),
'person2' =>
array (
),
'person3' =>
array (
),
);
if(array_sum(array_map(function($v){return !empty($v);}, $form_values)) === 0)
{
// empty
}
else
{
// not empty
}
Use a loop that tests each nested array. If none of them are non-empty, the whole array is empty.
$is_empty = true;
foreach ($form_values as $val) {
if (!empty($val)) {
$is_empty = false;
break;
}
}
<?php
$data =
[
'pig' => [],
'hog' => [],
'sow' => []
];
$all_empty = array_filter($data) === [];
var_dump($all_empty);
Output:
bool(true)
From the manual for array_filter:
If no callback is supplied, all empty entries of array will be
removed. See empty() for how PHP defines empty in this case.
Note that if an item was deemed as empty, like an empty string, it would still return true. This test may not be strict enough.
More explicitly:
if (array_filter($data, function($v) {return $v !== []; }) === []) {}
Filter out all items that aren't the empty array. What we'll be left with is an empty array if all items are an empty array.
Or search and compare:
if (array_keys($data, []) == array_keys($data)) {}
Check keys belonging to items containing the empty array match the keys of the array. Or rather all items (if they exist) are the empty array.
Note that an empty array will also satisfy the three solutions above.
I have this PHP array:
$this->user_list = array( 0 => 'Not paid',1 => 'Not paid', 2 => 'Not paid', 7 => 'Waiting, 15 => 'Waiting', 10 => 'Cancelled' );
How can I simplify this array as the id numbers are different, but some of them have same status?
I tried it like this:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
but it doesn't work as expected.
Basically I want to achieve this:
echo $this->user_list[15] should give me Waiting, echo $this->user_list[10] should give me Cancelled, etc. So this is working in my first array very well, I am just thinking about grouping duplicate names there.
As mentioned by other contributors, there is no native support in the PHP grammar for your intended use case. As clearly stated in the PHP: Arrays documentation:
An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments.
So basically each element in an array is a key => value pair, which means you cannot associate multiple keys to a single element.
This also explains why your first tentative didn't work:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
If you don't specify a key for an element, PHP uses a progressive index (0, 1, ...). So basically in the example above, the first zero is not actually a key, but a value, and PHP binds it to the key = 0. Maybe it could be easier for you to understand how it works if you print a var_dump or print_r of $this->user_list. You would get something similar to the following structure (NOTE: I have simplified the structure to make it more clear):
[
0 => [
0 => 0
1 => 1
2 => "Not paid"
],
1 => [
0 => 7,
15 => "Waiting"
],
10 => "Cancelled"
]
So how do we resolve this problem? Well... actually there is no need to contort the structure by swapping keys with values as other contributors seem to suggest. Changing the structure might simplify your "data entry" work but might also create big issues in other parts of the program because who knows, maybe accessing the invoice data by "ID" is simply more efficient than by "status" ... or something.
Since PHP does not provide such a feature out of the box, I believe a better solution would be to develop our own function; a good starting point could be the one in the example below.
function explode_array($config, $sep = ',') {
$res = [];
foreach($config as $configKey => $value) {
// split key values
$keys = explode($sep, $configKey);
foreach($keys as $key) {
$res[$key] = $value;
}
}
return $res;
}
$config = [
'0,1,2' => 'Not paid',
'7,15' => 'Waiting',
'10' => 'Cancelled'
];
$myArr = explode_array($config);
print_r($myArr);
The idea is quite simple: since we cannot use an array as key we leverage the next best data type, that is a CSV string. Please note there is no error handling in the above code, so the first thing you may want to do is adding some validation code to the explode_array (or however you wish to name it) function.
you should use like this. if id number is invoice id or something else and other value is there status about it.
$arr = array(
'Not paid' => [0,1,2] ,
'Waiting' => [5,6],
'Cancelled' =>[8]
);
foreach($arr as $key => $val){
foreach($val as $keys => $vals){
echo "invoiceid ".$vals ." status ".$key;
echo"<br>";
}
}
// for only one status you can use like this
foreach($arr['Not paid'] as $key => $val){
echo $val;
echo"<br>";
}
just try to run this and check output.
PHP has no built-in function or structure for handling cases like this. I'd use a simple array value-cloning function to map your duplicates. Simply have one instance of each status, then map the aliases, and then run a function that clones them in. As follows:
// Status list:
$ulist = [ 0 => 'Not paid', 7 => 'Waiting', 10 => 'Cancelled' ];
// Alternative IDs list, mapped to above source IDs:
$aliases = [ 0 => [1,2], 7 => [15] ];
// Function to clone array values:
function clone_values(array &$arr, array $aliases)
{
foreach($aliases as $src => $tgts) {
foreach($tgts as $tgt) {
$arr[$tgt] = $arr[$src];
}
}
ksort($arr); // If the order matters
}
// Let's clone:
clone_values($ulist, $aliases);
This results in the following array:
array(6) {
[0] · string(8) "Not paid"
[1] · string(8) "Not paid"
[2] · string(8) "Not paid"
[7] · string(7) "Waiting"
[10] · string(9) "Cancelled"
[15] · string(7) "Waiting"
}
....which can be accessed as you expect, here $ulist[2] => Not paid, etc. If the use case is as simple as illustrated in the OP, I'd personally just spell it out as is. There's no dramatic complexity to it. However, if you have dozens of aliases, mapping and cloning begins to make sense.
As said in the comments, you can't have multiple keys with one value. The best way is to use the keyword => [ number, number, number...] construction.
//set a result array
$result = [];
//loop the original array
foreach ( $this->user_list as $number => $keyword ){
//if the keyword doesn't exist in the result, create one
if(!isset ( $result [ $keyword ] ) ) $result[ $keyword ] = [];
//add the number to the keyword-array
$result[ $keyword ] [] = $number;
}
I am running this code from php.net example
<?php
$array1 = array(0, 1, 2);
$array2 = array("00", "01", "2");
$result = array_diff_assoc($array1, $array2);
print_r($result);
?>
Output of which I am getting :
Array
(
[0] => 0
[1] => 1
)
php.net says : Two values from key => value pairs are considered equal only if (string) $elem1 === (string) $elem2 .
whereas in the example it considers 2 === "2".
How is this happening ? Please Explain ?
Might be the case
If $ele1 is casting integer into string i.e. 2 to "2", then why it is comparing with === operator. There might be 2 == "2" better option and we don't need to cast it into string. Please correct me, if I am wrong ?
If $ele1 is casting integer into string i.e. 2 to "2", then why it is comparing with === operator. There might be 2 == "2" better option and we don't need to cast it into string. Please correct me, if I am wrong ?It's just the way it is implemented.
If you want another comparision function use array_diff_uassoc()
I can do this, I'm just wondering if there is a more elegant solution than the 47 hacked lines of code I came up with...
Essentially I have an array (the value is the occurrences of said string);
[Bob] => 2
[Theresa] => 3
[The farm house] => 2
[Bob at the farm house] => 1
I'd like to iterate through the array and eliminate any entries that are sub-strings of others so that the end result would be;
[Theresa] => 3
[Bob at the farm house] => 1
Initially I was looping like (calling this array $baseTags):
foreach($baseTags as $key=>$count){
foreach($baseTags as $k=>$c){
if(stripos($k,$key)){
unset($baseTags[$key]);
}
}
}
I'm assuming I'm looping through each key in the array and if there is the occurrence of that key inside another key to unset it... doesn't seem to be working for me though. Am I missing something obvious?
Thank you in advance.
-H
You're mis-using strpos/stripos. They can return a perfectly valid 0 if the string you're searching for happens to be at the START of the 'haystack' string, e.g. your Bob value. You need to explicitly test for this with
if (stripos($k, $key) !== FALSE) {
unset(...);
}
if strpos/stripos don't find the needle, they return a boolean false, which under PHP's normal weak comparison rules is equal/equivalent to 0. Using the strict comparison operators (===, !==), which compare type AND value, you'll get the proper results.
Don't forget as-well as needing !== false, you need $k != $key so your strings don't match themselves.
You have two problems inside your code-example:
You combine each key with each key, so not only others, but also itself. You would remove all entries so because "Bob" is a substring of "Bob", too.
stripos returns false if not found which can be confused with 0 that stands for found at position 0, which is also equal but not identical to false.
You need to add an additional check to not remove the same key and then fix the check for the "not found" case (Demo):
$baseTags = array(
'Bob' => 2,
'Theresa' => 3,
'The farm house' => 2,
'Bob at the farm house' => 1,
);
foreach ($baseTags as $key => $count)
{
foreach ($baseTags as $k => $c)
{
if ($k === $key)
{
continue;
}
if (false !== stripos($k, $key))
{
unset($baseTags[$key]);
}
}
}
print_r($baseTags);
Output:
Array
(
[Theresa] => 3
[Bob at the farm house] => 1
)
Here is the array
$anArray = array(
"theFirstItem" => "a first item",
if(True){
"conditionalItem" => "it may appear base on the condition",
}
"theLastItem" => "the last item"
);
But I get the PHP Parse error, why I can add a condition inside the array, what's happen??:
PHP Parse error: syntax error, unexpected T_IF, expecting ')'
Unfortunately that's not possible at all.
If having the item but with a NULL value is ok, use this:
$anArray = array(
"theFirstItem" => "a first item",
"conditionalItem" => $condition ? "it may appear base on the condition" : NULL,
"theLastItem" => "the last item"
);
Otherwise you have to do it like that:
$anArray = array(
"theFirstItem" => "a first item",
"theLastItem" => "the last item"
);
if($condition) {
$anArray['conditionalItem'] = "it may appear base on the condition";
}
If the order matters, it'll be even uglier:
$anArray = array("theFirstItem" => "a first item");
if($condition) {
$anArray['conditionalItem'] = "it may appear base on the condition";
}
$anArray['theLastItem'] = "the last item";
You could make this a little bit more readable though:
$anArray = array();
$anArray['theFirstItem'] = "a first item";
if($condition) {
$anArray['conditionalItem'] = "it may appear base on the condition";
}
$anArray['theLastItem'] = "the last item";
If you are making a purely associative array, and order of keys does not matter, you can always conditionally name the key using the ternary operator syntax.
$anArray = array(
"theFirstItem" => "a first item",
(true ? "conditionalItem" : "") => (true ? "it may appear base on the condition" : ""),
"theLastItem" => "the last item"
);
This way, if the condition is met, the key exists with the data. If not, it's just a blank key with an empty string value. However, given the great list of other answers already, there may be a better option to fit your needs. This isn't exactly clean, but if you're working on a project that has large arrays it may be easier than breaking out of the array and then adding afterwards; especially if the array is multidimensional.
Your can do it like this:
$anArray = array(1 => 'first');
if (true) $anArray['cond'] = 'true';
$anArray['last'] = 'last';
However, what you want is not possible.
Try this if you have associative array with different keys:
$someArray = [
"theFirstItem" => "a first item",
] +
$condition
? [
"conditionalItem" => "it may appear base on the condition"
]
: [ /* empty array if false */
] +
[
"theLastItem" => "the last item",
];
or this if array not associative
$someArray = array_merge(
[
"a first item",
],
$condition
? [
"it may appear base on the condition"
]
: [ /* empty array if false */
],
[
"the last item",
]
);
There's not any magic to help here. The best you can do is this:
$anArray = array("theFirstItem" => "a first item");
if (true) {
$anArray["conditionalItem"] = "it may appear base on the condition";
}
$anArray["theLastItem"] = "the last item";
If you don't care specifically about the order of the items, it gets a little more bearable:
$anArray = array(
"theFirstItem" => "a first item",
"theLastItem" => "the last item"
);
if (true) {
$anArray["conditionalItem"] = "it may appear base on the condition";
}
Or, if the order does matter and the conditional items are more than a couple, you can do this which could be considered more readable:
$anArray = array(
"theFirstItem" => "a first item",
"conditionalItem" => "it may appear base on the condition",
"theLastItem" => "the last item",
);
if (!true) {
unset($anArray["conditionalItem"]);
}
// Unset any other conditional items here
You can assign all values and filter empty keys from the array at once like this:
$anArray = array_filter([
"theFirstItem" => "a first item",
"conditionalItem" => $condition ? "it may appear base on the condition" : NULL,
"theLastItem" => "the last item"
]);
This allows you avoid the extra conditional after the fact, maintain key order, and imo it's fairly readable. The only caveat here is that if you have other falsy values (0, false, "", array()) they will also be removed. In that case you may wish to add a callback to explicitly check for NULL. In the following case theLastItem won't get unintentionally filtered:
$anArray = array_filter([
"theFirstItem" => "a first item",
"conditionalItem" => $condition ? "it may appear base on the condition" : NULL,
"theLastItem" => false,
], function($v) { return $v !== NULL; });
You can do it like this:
$anArray = array(
"theFirstItem" => "a first item",
(true ? "conditionalItem" : "EMPTY") => (true ? "it may appear base on the condition" : "EMPTY"),
"theLastItem" => "the last item"
);
unset the EMPTY array item if the condition is false
unset($anArray['EMPTY']);
Its pretty simple. Create array with essential elements. Then add conditional elements to the array. Now add other elements if required.
$anArray = array(
"theFirstItem" => "a first item"
);
if(True){
$anArray+=array("conditionalItem" => "it may appear base on the condition");
}
$more=array(
"theLastItem" => "the last item"
);
$anArray+=$more;
You modify this code to make it even more shorter,, i have just given elaborated code to make it self explantory. No NULL element, no empty string, put you item anywhere you want, no hassel.
You can use array_merge
$result = array_merge(
['apple' => 'two'],
(true ? ['banana' => 'four'] : []),
(false ? ['strawberry' => 'ten'] : [])
);
print_r($result);
// Output:
/*
Array
(
[apple] => 'two'
[banana] => 'four'
)
*/
Drawback of array_merge: Values in an array with numeric keys will be renumbered with incrementing keys starting from zero in the result array. You can use the union operator instead to maintain the original numeric keys
Or by using Array Union Operator
$result = ['apple' => 'two'] + (true ? ['banana' => 'four'] : []) + (false ? ['strawberry' => 'ten'] : []);
print_r($result);
// Output:
/*
Array
(
[apple] => 'two'
[banana] => 'four'
)
*/
Note: When using the union operator, remember that for keys that exist in both arrays, the elements from the left-hand array will be used, and the matching elements from the right-hand array will be ignored.