I am using PHP 5.5.12.
I have the following multidimensional array:
[
{
"id": 1,
"type":"elephant",
"title":"Title of elephant"
},
{
"id": 2,
"type":"tiger",
"title":"Title of tiger"
},
{
"id": 3,
"type":"lion",
"title":"Title of lion",
"children":[{
"id": 4,
"type":"cow",
"title":"Title of cow"
},
{
"type":"elephant",
"title":"Title of elephant"
},
{
"type":"buffalo",
"title":"Title of buffalo"
}]
}
]
I am iterating this array using foreach loop.
The array key type must be in elephant, tiger and lion. If not, then the result should return false.
How can I achieve this?
So you want to check if your $myArray contains a value or not:
// first get all types as an array
$type = array_column($myArray, "type");
// specify allowed types values
$allowed_types = ["lion", "elephant", "tiger"];
$count = count($type);
$illegal = false;
// for loop is better
for($i = 0; $i < $count; $i++)
{
// if current type value is not an element of allowed types
// array, then both set the $illegal flag as true and break the
// loop
if(!in_array($type[$i], $allowed_types)
$illegal = true;
break;
}
Since you're using PHP5.5.12, you can make use of array_column.
$arr = json_decode($json, true);
//Walk through each element, only paying attention to type
array_walk( array_column($arr, 'type'), function($element, $k) use(&$arr) {
$arr[$k]['valid_type'] = in_array($element, array('lion', 'tiger', 'elephant'));
});
From here, each element in the array ($arr) will have a new key valid_type with a boolean value - 1 if the type is valid, 0 if it isn't.
https://eval.in/350322
Is this something that you are looking for?
foreach($your_array as $item) {
if (!array_key_exists('type', $item)) {
return FALSE;
}
}
function keyExists($arr, $key) {
$flag = true;
foreach($arr as $v) {
if(!isset($v[$key])) {
$flag = false;
break;
}
}
return $flag;
}
Hope this helps :)
Related
In my recursive function I can't generate a unique id (via an incremented variable) for my multidimensional array, here is what I tried.
I need to assign a unique ID for each directory name I find: $data[] = ['id'=>$i,'text'=>$dir];
function scandir_rec($root, $i)
{
$data = [];
if (!is_dir($root)) {
return;
}
$dirs = scandir($root);
foreach ($dirs as $dir) {
$i++;
if ($dir == '.' || $dir == '..') {
continue;
}
$path = $root . '/' . $dir;
if (is_file($path)) {
continue;
}
if (is_dir($path)) {
$nodes = scandir_rec($path, $i); // <--- unique ID
if (!empty($nodes)) $chunk['children'] = $nodes;
}
$data[] = [ 'id' => $i, 'text' => $dir ]; // <--- unique ID
}
return $data;
}
$rootDir = '/var/www/html';
$i = 0;
$json_data = json_encode(scandir_rec($rootDir, $i), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); // <--- unique ID
file_put_contents('tree.json', $json_data);
Desired result
[
{
"id":1,
"text":"Root folder",
"children":[
{
"id":2,
"text":"Child folder 1"
},
{
"id":3,
"text":"Child folder 2"
}
]
},
{
"id":4,
"text":"Root folder"
},
{
"id":5,
"text":"Root folder",
"children":[
{
"id":6,
"text":"Child folder 1"
},
{
"id":7,
"text":"Child folder 2"
}
]
}
]
Personally I hate returning from recursive functions, it tends to get weird unless you are returning a status for the caller to do something with. Instead, it is often easier to use byref parameters.
I've commented the code below, hopefully it makes sense. We're passing two parameters byref, one is "the current array to add stuff to" and the other is "the global index". We're also keeping track of depth because you are treating the root differently than children.
function scandir_rec(string $root, array &$current_parent, int &$global_index, int $depth = 0): void
{
// Bail early if we don't have a directory
if (!is_dir($root)) {
return;
}
// Get the children
$dirs = scandir($root);
// This is the local child index and resets for successive calls
$local_index = 1;
foreach ($dirs as $dir) {
// Once again, bail early if we can
if ($dir === '.' || $dir === '..') {
continue;
}
// The next path to scan
$next_path = $root . '/' . $dir;
// Make sure it isn't a file
if (is_file($next_path)) {
continue;
}
// Everyone gets this
$current_node = [
// Set the ID and increment so that the next gets a bigger one
'id' => $global_index++,
// Root node is treated differently thant children
'text' => 0 === $depth ? 'Root node' : "Child node ${local_index}",
];
// If the next node is a directory
if (is_dir($next_path)) {
// Create a temporary holder for the contents
$children = [];
// Scan again, incrementing the depth by one
scandir_rec($next_path, $children, $global_index, ++$depth);
// If the above did something to our array, append to our local (the master)
if ($children) {
$current_node['children'] = $children;
}
}
// Append the current node
$current_parent[] = $current_node;
}
}
To call this, remember that it doesn't return so we need to run it by itself, We also want to pass the global index in by ref so we need to create a variable to hold it.
$current_parent = [];
$global_index = 1;
scandir_rec('/var/www', $current_parent, $global_index);
$json_data = json_encode($current_parent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
This code is written against PHP 7.4 do you might need to tweak a couple of things if you are on an older version.
edit
In hindsight, I was taking your text too literally, I think. You can probably get rid of the $depth parameter completely and just set your text to:
'text' => $root,
I'm running this from Windows and I just ran a quick test folder which produced:
[
{
"id": 1,
"text": "/Dell",
"children": [
{
"id": 2,
"text": "/Dell/25tgw",
"children": [
{
"id": 3,
"text": "/Dell/25tgw/RealtekHDAudio"
},
{
"id": 4,
"text": "/Dell/25tgw/RealtekHDAudio"
},
{
"id": 5,
"text": "/Dell/25tgw/RealtekHDAudio"
},
{
"id": 6,
"text": "/Dell/25tgw/RealtekHDAudio"
}
]
}
]
},
{
"id": 7,
"text": "/Dell",
"children": [
{
"id": 8,
"text": "/Dell/UpdatePackage"
}
]
},
{
"id": 9,
"text": "/Dell"
}
]
The issue is that each recursive call starts it's own count. Let's say $i is 10 inside the loop at a given time. You start a recursive call that increases it up to 15 but when the call ends and returns to parent function, $i there is still 10.
Perhaps the simplest fix is to pass the counter by reference:
function scandir_rec($root, &$i)
{
}
Another solution is to avoid recursion altogether. PHP has builtin iterators that can walk a three without recursion, e.g.:
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootDir),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($files as $name => $object) {
echo sprintf("[%s] %s\n", $object->isDir() ? 'DIR' : ' ', $name);
}
I'am stuck on assigning pid's (parent id's) on 3rd element. I mean in setParent() if I return $array before call recursive function, then I see that "PC" and "Laptop" are having correct pid's.
If I run all script, then I get $newArr is null. PHP 7.3, Laravel 6.
I want to make this:
[
['pc', ['pc',null, null] ,'id'=>1,'pid'=>1],
['laptop', ['pc','laptop', null] ,'id'=>2,'pid'=>1],
['acc', ['pc','acc', null] ,'id'=>3,'pid'=>1],
['bags', ['pc','acc', 'bags'] ,'id'=>4,'pid'=>3],
['adapter', ['pc','acc', 'adapter'] ,'id'=>5,'pid'=>3],
['clothes', ['clothes',null,null] ,'id'=>6,'pid'=>6]
];
From that :
function test3(){
$array = [
['pc', ['pc',null, null] ,'id'=>1,'pid'=>0],
['laptop', ['pc','laptop', null] ,'id'=>2,'pid'=>0],
['acc', ['pc','acc', null] ,'id'=>3,'pid'=>0],
['bags', ['pc','acc', 'bags'] ,'id'=>4,'pid'=>0],
['adapter', ['pc','acc', 'adapter'] ,'id'=>5,'pid'=>0],
['clothes', ['clothes',null,null] ,'id'=>6,'pid'=>0]
];
$newArr = $this->setParent($array);
return response()->json([$newArr]);
}
function setParent($array, $goStop='go'){
if($goStop == 'go'){
$foundPids = 0;
foreach($array as $k => $v){
if( $v["pid"] == 0) $foundPids++;
}
if( $foundPids == 0){
return $this->setParent($array, 'stop'); // or return $array;
}
// parent search
foreach($array as $k1 => $v1){
if ($v1['pid']!=0)
break;
$keyInd = array_search($v1[0] , $v1[1]);// $v1 looks like {"0":"pc","1":["pc",null,null],"id":1,"pid":0}
if($keyInd == 0){
$array[$k1]['pid'] = $v1['id']; // PC updated
}
if($keyInd > 0){
$parentName = $v1[1][$keyInd-1];
$IdToWriteInPid = 0;
foreach ($array as $k1inner => $v1inner){
if($v1inner[$keyInd-1] == $parentName){
$IdToWriteInPid = $v1inner['id'];
}
}
$array[$k1]['pid'] = $IdToWriteInPid; // Laptop updated
//
// if uncomment, then i see that category PC having pid 1
// and category Laptop having pid 1. It is ok.
//
// return $array;
//
return $this->setParent($array, 'go' );
}
}
}
else return $array;
}
You could build hierarchy paths for each of your entries using implode, then make use of array_reduce to:
index your entries by their path,
alter the pid of each subsequent entry if their parent's path was found.
Finally, array_values will switch back your array's indexes to numerical format.
Note: this assumes the parents are always defined before their children, like in your sample data.
Note 2: this also assumes the names such as 'pc' cannot contain forward slashes (/). Feel free to change the separator otherwise.
function fillParentIds(array $input): array
{
return array_values(array_reduce($input, static function (array $entriesByPath, array $entry): array {
$hierarchy = array_filter($entry[1]);
$pathToParent = implode('/', array_slice($hierarchy, 0, -1));
$pathToEntry = implode('/', $hierarchy);
$entry['pid'] = array_key_exists($pathToParent, $entriesByPath)
? $entriesByPath[$pathToParent]['id']
: $entry['id'];
$entriesByPath[$pathToEntry] = $entry;
return $entriesByPath;
}, []));
}
Usage:
$withParentIds = fillParentIds($input);
Demo: https://3v4l.org/DeORi
For example, I have a list of strings like this ["889003521", "level5", "120", "1.1", "500", "10", "false"] and I need convert each element to real type, like this [889003521. "level5", 120, 1.1, 500, 10, false], what is the best way to achive this? Thanks.
Updated:
#04FS I found a way to make a conversion for int/float, not sure if it's the best way
if (is_numeric($value)) {
$floatVal = floatval($value);
if($floatVal && intval($floatVal) != $floatVal){
return $floatVal;
}
return (int)$value;
}
but not sure how to convert to boolean.
You can use json_decode for that. Please bear in mind that for string it can return null when decoding, so we're checking whether the converted value is not null.
$arr = ["889003521", "level5", "120", "1.1", "500", "10", "false"];
$ret = [];
foreach($arr as $val) {
$converted = \json_decode($val);
$ret[] = $converted ?? $val;
}
And this is the content of the $ret array:
0 = {int} 889003521
1 = "level5"
2 = {int} 120
3 = {float} 1.1
4 = {int} 500
5 = {int} 10
6 = false
in addition to tomfor's answer (thanks for that), here is a recursive function to set the correct type for associative arrays / json objects:
function convertType($obj) {
if (gettype($obj) == 'array') {
foreach ($obj as $k => $v) {
$obj[$k] = convertType($v);
}
return $obj;
}
$converted = \json_decode($obj);
return $converted ?? $obj;
}
u can use it like this:
$jsonWithCorrectTypes = convertType($someJson);
I have following php array object:
"requests": [
{
"id": 111,
"time: 123123123,
"data": "testc"
},
{
"id": 200 ,
"time: 433123123,
"data": "testb"
},
{
"id": 300 ,
"time: 33123123,
"data": "testb"
}
]
I want to sort it by requests->time only. How to do it?
I don't want sort by id. All data should be sort as per time ASC.
supposing you have that array in a $requests variable, the following should work
<?php
function cmp($a, $b)
{
return strcmp($a["time"],$b["time"]);
}
usort($requests, "cmp");
//print for test
while (list($key, $value) = each($requests)) {
echo "\$requests[$key]: " . $value["time"] . "\n";
}
?>
You have to use your own sort method with usort()
Solution :
<?php
function sortByTimeASC($a, $b)
{
if ($a->time == $b->time) {
return 0;
}
return ($a->time < $b->time) ? -1 : 1;
}
$a = json_decode('[{"id": 111,"time": 123123123,"data": "testc"},{ "id":200 ,"time":433123123,"data":"testb"},{"id":300,"time":33123123,"data":"testb"}]');
usort($a, "sortByTimeASC");
print_r($a);
?>
Live example
I am checking that certain elements in sub-arrays in a multidimensional array are not equal to a value and un-setting the array with that value from the multi array. I built a function so that I could easily implement this, however it doesn't appear to be working.
function multi_unset($array, $unset) {
foreach($array as $key=>$value) {
$arrayU = $array[$key];
$check = array();
for($i = 0; $i < count($unset); $i++) { // produces $array[$key][0, 2, 3]
array_push($check, $arrayU[$unset[$i]]);
}
if(in_array("-", $check)) {
unset($array[$key]);
}
}
return $array;
}
$arr = array(array("-", "test", "test", "test"), array("test", "test", "test", "test"));
$unset = array(0, 2, 3); // keys in individual arrays to check
multi_unset($arr, $unset);
print_r($arr); // Should output without $arr[0]
In this case, I'm checking if each sub-array has a "-" value in it and un-setting the array from the multi array. I am only checking specific keys in the sub-arrays (0, 2, 3) however it outputs an array without any changes. I figured I must have some scoping wrong and tried to use "global" everywhere possible, but that didn't seem to fix it.
Modified your version a bit and handled the return value.
function multi_unset($array, $unset)
{
$retVal = array();
foreach($array as $key => $value)
{
$remove = false;
foreach($unset as $checkKey)
{
if ($value[$checkKey] == "-")
$remove = true;
}
if (!$remove)
$retVal[] = $value;
}
return $retVal;
}
$arr = array(array("-", "test", "test", "test"), array("test", "test", "test", "test"));
$unset = array(0, 2, 3);
$arr = multi_unset($arr, $unset);
print_r($arr);
You may want to do some reading into Passing by Reference vs passing by value in PHP.
Heres some code that works with the given data set....
// Note the pass by reference.
function multi_unset(&$array, $unset) {
foreach($array as $pos => $subArray) {
foreach($unset as $index) {
$found = ("-" == $subArray[$index]);
if($found){
unset($subArray[$index]);
// Ver 2: remove sub array from main array; comment out previous line, and uncomment next line.
// unset($array[$pos]);
}
$array[$pos] = $subArray; // Ver 2: Comment this line out
}
}
//return $array; // No need to return since the array will be changed since we accepted a reference to it.
}
$arr = array(array("-", "test", "test", "test"), array("test", "test", "test", "test"));
$unset = array(0, 2, 3);
multi_unset($arr, $unset);
print_r($arr);