Take the following code into consideration:
$files = array('1.js', '1.css', '2.js', '2.css', '3.js', '3.png');
$extensions = array();
foreach ($files as $file)
{
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (empty($extensions[$extension]) === true)
{
$extensions[$extension] = 0;
}
++$extensions[$extension];
}
arsort($extensions); // array('js' => 3, 'css' => 2, 'png' => 1)
$common_extension = key($extensions); // js
The code seems to work as I want it to (I still need to check what happens in case there is a draw, but that's irrelevant for this question). I'm looking for a more efficient (and compact way) of rewriting the above snippet, the closest I've got so far is:
$files = array('1.js', '1.css', '2.js', '2.css', '3.js', '3.png');
$extensions = array_count_values(array_map('strtolower', preg_replace('~^.*[.](.*)$~', '$1', $files)));
arsort($extensions, SORT_NUMERIC);
$common_extension = key($extensions);
But this loops through the array 3 times and that preg_replace() is not bulletproof... Any ideas?
I would do something like this:
<?php
function getCommon($array, $result = array()) {
foreach ($array as $k => $v) { $array[$k] = strtolower(pathinfo($v, PATHINFO_EXTENSION)); }
$ext = array_count_values($array); arsort($ext,SORT_NUMERIC);
$k = array_keys($ext); $k0 = $k[0];
if ($ext[$k0] > $ext[$k[1]]) { $result[] = $k0; }
else { foreach ($ext as $k => $v) { if ($v == $ext[$k0]) { $result[] = $k; } } }
return $result;
}
$files = array('1.js', '2.js', '3.png', '4.css');
print_R($files);
print_R(getCommon($files));
$files2 = array('1.js', '2.js', '3.png', '4.png', '5.css');
print_R($files2);
print_R(getCommon($files2));
?>
This will return an array, where values will be common extensions, even if they are many.
Note: Don't use regex when your job basic PHP functions can do - regex is too resource-consuming compared to php built-in functions.
I have thought about this question for quite some time now and I think your first snippet is pretty much the answer to it. It is not like shorter code is faster code. That code is pretty fast and scaling linearly to greater arrays. It is pretty much a complexity of O(n) plus the sorting algorithm arsort (i have no clue what method it is using, but i expect it to be faster than a self written one). The only thing I can offer is this little function containing your snippets and a little tweak in the case the extension is not in the array.
function count_ext($array){
$ret = array();
foreach($array as $ext){
$ext = strtolower(pathinfo($ext, PATHINFO_EXTENSION));
if( !isset($ret[$ext]) ) $ret[$ext] = 0;
$ret[$ext]++;
}
arsort($ret);
return $ret;
}
I don't know how many elements your arrays have and how time-critical your use case is, but this method will do well.
Related
I'm need an array of all the folders in a directory, im using laravel to get an array of folders in the directory using the below code.
Storage::allDirectories('/public');
this returns an array like below.
$directories = [
"Gallery",
"Images",
"Images/Proof",
"Images/Proof/Another",
];
I need a way to remove duplicate parent directories, so for example Images will be remove if Images/Proof/Another exists or something like that, i just need to build an array of a directory and its subfolders as one array, if that makes sense.
so for example:
$directories = [
["Gallery"],
["Images/Proof/Another"],
];
Or something like this, I can't think of a way to get this to work.
I'm building a custom media manager component if anyone was wondering, some exist but i have no control over them so i built my own.
all help is appreciated.
Can you post the code you have tried so far
For 1 i can't get my head around the logic, thats why im asking what i've done "Which i dont see how it would help at all"
foreach (Storage::allDirectories('/public') as $folder)
{
$all_folders[] = $exploded('/',$folder);
}
Im trying to separate the array into smaller arrays and check each exploded bit against another. But this has me running in circles.
Do like below (Easiest way):-
$final_array = [];
foreach($directories as $dir){
$final_array[explode('/',$dir)[0]] = $dir;
}
$final_array =array_values($final_array);
Output:- https://eval.in/912417
Or:-
$final_array = [];
foreach($directories as $dir){
$final_array[explode('/',$dir)[0]][0] = $dir;
}
$final_array =array_values($final_array);
Output:-https://eval.in/912418
If you rsort (descending) it and foreach it you can then use preg_grep to see if it exsists in your new array.
If not add it.
$directories = [
"Gallery",
"Images",
"Images/Proof",
"Images/Proof/Another",
];
Rsort($directories);
$new=[];
Foreach($directories as $val){
If(!preg_grep("~" . $val. ".*~",$new)){
$new[] = $val;
}
}
Var_dump($new);
https://3v4l.org/0EnZ3
Preg_grep will look for the pattern and see if it exsists in the array.
Since I loop descending it will first look at Images/Proof/Another then add it to the list because it's not there.
Next iteration will look at Images/Proof/ and since preg_grep has pattern Images/Proof/.* it will be true thus not add it in the list.
Then the same with images.
You can just filter array using array_filter function.
<?php
$directories = [
"Gallery",
"Images",
"Images/Proof",
"Images/Proof/Another",
];
$filtered = array_filter($directories, function($v) use ($directories) {
foreach($directories as $dir) {
if(strpos($dir, $v . "/") === 0) {
return false;
}
}
return true;
});
var_dump($filtered);
Not so elegant, but clear to read.
Quick and dirty, but tested and works
function removeRedundantParents($directories)
{
foreach ($directories as $key => &$val)
{
foreach ($directories as $i => &$v)
{
if ($val !== $v && substr($v, 0, strlen($val)) == $val)
{
unset($directories[$key]);
continue;
}
}
}
return $directories;
}
I achieved this by removing the row if it satisfies the logic.
Mutative, but satisfies the requirement correctly.
Tested, works properly.
check out https://eval.in/912439 for the snippet and output
Code
$directories = array_flip($directories);
foreach ($directories as $dir => $key) {
$parent_dir = preg_match('/(.*)\//', $dir, $match);
if (isset($match[1])) {
unset($directories[$match[1]]);
}
}
$directories = array_keys($directories);
I create a function for remove child dirs on arrays structure. USer for error or mistake, has put on scenario, subdirs child of a parent dir already in config list.
/dir_a/subdir_a
/dir_b/subdir_a/subdir_a
/dir_b/subdir_a/subdir_b
/dir_b/
/dir_c/subdir_a/
...
Code
$local_pre_order = array_unique($local_sync);
asort($local_pre_order);
$local_order = array();
foreach ($local_pre_order as $value)
{
$repeat = false;
foreach ($local_pre_order as $value2)
{
$pos = strpos($value,$value2);
if (($pos !== false ) && ($value != $value2) && ($pos == 0)) {
$repeat = true;
break;
}
}
if (!$repeat) {
$local_order[] = $value;
}
}
Sort OK
/dir_a/subdir_a
/dir_b/
/dir_c/subdir_a/
I think it's a no good programing. Work but not fine. IMHO. Any ideas for best code?
Sorting is the way to go. Note that when sorted, all the child subdirs will be immediatelly after their parents. So it's just a matter of avoiding adding subdirs if the last added dir has a common prefix with it.
PHP isn't my first language, so please don't mind this non-idiomatic code.
$local_sync = [
"/dir_a/subdir_a",
"/dir_b/subdir_a/subdir_a",
"/dir_b/subdir_a/subdir_b",
"/dir_b/",
"/dir_c/subdir_a/"
];
$copy = $local_sync;
asort($copy);
$output = array();
foreach ($copy as $value)
if (strpos($value, end($output)) !== 0)
$output[] = $value;
print_r($output)
It's best not to rely on the array's structure, don't rely on the array being sorted or a comparison between the current path and the previous. What you want to do is use dirname () to get each path's directory name, and search the output array for each path, only adding the path to the output array if it's not already in the output array.
Like so...
$directory_paths = array (
'/dir_a/subdir_a',
'/dir_b/subdir_a/subdir_a',
'/dir_b/subdir_a/subdir_b',
'/dir_b/',
'/dir_c/subdir_a/'
);
$sorted_paths = $directory_paths;
asort ($sorted_paths);
$output = array ();
foreach ($sorted_paths as $key => $path) {
$directory = $path;
$levels = substr_count (trim ($path, '/'), '/') - 1;
for ($i = 0; $i < $levels; $i++) {
$directory = dirname ($directory);
}
if (in_array ($directory, $sorted_paths)) {
$output[$key] = $path;
}
}
ksort ($output);
$output = array_values ($output);
echo print_r ($output, true);
I have several files uploaded with a form that is processed by a PHP script. There are several "categories" that I need to have handles separate ways. In other words, I need some of the files in the $_FILES array to be stored someplace, and some others stored in other places.
So what I need is all files (array keys) that are not $_FILES['filename_form'] to be stored / used separably.
The reason why I need this is because the amount of files being uploaded are dynamic, but always either $_FILES['filename_type1'] or $_FILES['filename_type2_*] where * is a number that increments when more files are input from the form (dynamically)
Might be a very simple solution, either I'm too tired or maybe I've been staring too much at the code, but all I know is I need a solution before tomorrow morning, so it's time sensitive =P
Appreciate all the help I can get.
Thank you!
$files = array();
$filenames = array();
foreach($_FILES as $name => $fileInfo)
{
if($name != 'filename_form')
{
$files[$name] = $fileInfo;
$filenames[] = $name;
}
}
How about:
foreach ( $_FILES as $key => $value )
if ( preg_match ( '/type2/', $key ) )
// do something with the type2
else if ( preg_match ( '/type1/', $key ) )
// do something with these
<?php
foreach ($_FILES as $key => $value) {
$a = explode('_', $key);
$defaultDir = 'myDefaultDirOrPathOrWhatever';
if ($a[1] == 'type2') {
$defaultDir = 'myOtherDirWhenMultipleFiles';
$incrementPassed = $a[2];
}
if (isset($incrementPassed)) unset($incrementPassed);
}
Here is an alternate way
$keys = array_keys($thearray);
$filteredArray = array_filter($keys, 'fiterKeys');
function filterKeys($item) {
if($item != 'valuetocheck') return true;
else return false;
}
$type1_files = array(); // Array of files of first type
$type2_files = array(); // Array of files of second type
$underfined_files = array(); // Array of files of unnormal type (who knows :-P)
foreach($_FILES as $name => $value){
if($name == 'type1'){
$type1_files[] = $value;
}
elseif($name == 'type2'){
$type2_files[] = $value;
}
else{
$undefined_files[] = $value;
}
}
After foreach you'll have your files devided into arrays, which you can process later as you wish
<?php
$files = array();
foreach ($_FILES as $filename => $fileinfo) {
if (strpos($filename, 'form') !== false) {
$files[$name] = $fileinfo;
unset($_FILES[$name]);
}
}
?>
Fastest method I can think of, should be faster then any of the previous answers.
For a homework assignment, I have to get all the .htm and .html files in the current and all sub directories, and I have to index them by counting all the words that appear in the files individually.
Here is how I would count the file once I find an html file in a directory:
$file = '.html';
$index = indexer($file);
echo '<pre>'.print_r($index,true).'</pre>';
function indexer($file) {
$index = array();
$find = array('/\r/','/\n/','/\t/','!',',','.','"',';', ':');
$replace = array(' ',' ',' ',' ',' ',' ',' ',' ',' ');
$string = file_get_contents($file);
$string = strip_tags($string);
$string = strtolower($string);
$string = str_replace($find, $replace, $string);
$string = trim($string);
$string = explode(' ', $string);
natcasesort($string);
$i = 0;
foreach($string as $word) {
$word = trim($word);
$ignore = preg_match('/[^a-zA-Z]/', $word);
if($ignore == 1) {
$word = '';
}
if( (!empty($word)) && ($word != '') ) {
if(!isset($index[$i]['word'])) {
$index[$i]['word'] = $word;
$index[$i]['count'] = 1;
} elseif( $index[$i]['word'] == $word ) {
$index[$i]['count'] += 1;
} else {
$i++;
$index[$i]['word'] = $word;
$index[$i]['count'] = 1;
}
}
}
unset($work);
return($index);
}
I just need to figure out first how to find all the htm or html files in the directories and then start using the above code on each htm/html file. Any help will be appreciated, thanks!
Well, because this is a homework assignment, I won't give you the code. But I can point you in the right direction. Usually for this type of thing, people with use a recursive function. Where a function calls itself.
This function should do the following:
Count all the lines of all the htm, and html files in the current directory.
Add these numbers up, and then add them to a global variable outside the function (just use global, you could return the number of lines each call, and add them up, but that is a pain in the butt)
call this function again for every folder in the current directory (just loop through them)
once you are back at the very start, reset the global variable, and return its value
The RecursiveDirectoryIterator is the best class in PHP to do this. It's flexible and fast.
Other alternative methods (not recursive) are described in "Directory to array with PHP". In my answer to that question, I timed the different methods given by other answers, but all of the solutions in PHP code are slower than using the PHP's SPL classes.
Here's an alternative using RecursiveIteratorIterator, RecursiveDirectoryIterator and pathinfo().
<?php
$dir = '/';
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::CHILD_FIRST);
foreach ( $iterator as $path )
if ( $path->isFile() && preg_match('/^html?$/i', pathinfo($path->getFilename(), PATHINFO_EXTENSION)) )
echo $path->getPathname() . PHP_EOL;
If you need to get the current working directory, you can use getcwd() (i.e. $dir = getcwd();).
To get the length of the content, you can do a few things. You could retrieve the contents of the file using file_get_contents and use strlen to calculate the length or str_word_count to count the words. Another option could be to use $path->getSize().
If you use an array to store the names and the sizes, you can then use a custom function and uasort to sort the array by sizes.
A more complete example:
<?php
function sort_by_size($a, $b)
{
if ( $a['size'] == $b['size'] )
return 0;
return ( $a['size'] < $b['size'] ? -1 : 1 );
}
$dir = '/';
$files = array();
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::CHILD_FIRST);
foreach ( $iterator as $path )
if ( $path->isFile() && preg_match('/^html?$/i', pathinfo($path->getFilename(), PATHINFO_EXTENSION)) )
$files[] = array(
'name' => $path->getPathname(),
'size' => $path->getSize()
);
uasort($files, sort_by_size);
The $files array can then be looped through using a foreach loop. It will contain both the pathname and the size.
Try Using glob function.
$files = glob('*.htm*');
foreach($files as $file) {
//code here
}
Edited:
function readDir($path) {
$files = glob($path . '*.*');
foreach ($files as $file) {
if (is_dir($file)) {
$html_files = array_merge((array) readDir($file . '/'), (array) $html_files);
}
if (in_array(strtolower(end(explode('.', $file))), array('html', 'htm'))) {
$html_files[] = $file;
}
}
return $html_files;
}
Just edited the answer, Try this. (Note: I haven't Not tested the code on any site.)
Thanks
Do you have any restrictions on the functions/classes you can use? If not, then check out RecursiveDirectoryIterator it will let you go through dirs recursively iterating over all the items in the directory. You could then match the extension on each item and if it matches basically do your counting.
An alternative approach to this would be to use glob while iterating over the directories which allows you to do a *.html search like you would use with with the *nix utility find.
As far as counting you might want to take look at str_word_count.
I am trying to create a multi-dimensional array whose parts are determined by a string. I'm using . as the delimiter, and each part (except for the last) should be an array
ex:
config.debug.router.strictMode = true
I want the same results as if I were to type:
$arr = array('config' => array('debug' => array('router' => array('strictMode' => true))));
This problem's really got me going in circles, any help is appreciated. Thanks!
Let’s assume we already have the key and value in $key and $val, then you could do this:
$key = 'config.debug.router.strictMode';
$val = true;
$path = explode('.', $key);
Builing the array from left to right:
$arr = array();
$tmp = &$arr;
foreach ($path as $segment) {
$tmp[$segment] = array();
$tmp = &$tmp[$segment];
}
$tmp = $val;
And from right to left:
$arr = array();
$tmp = $val;
while ($segment = array_pop($path)) {
$tmp = array($segment => $tmp);
}
$arr = $tmp;
I say split everything up, start with the value, and work backwards from there, each time through, wrapping what you have inside another array. Like so:
$s = 'config.debug.router.strictMode = true';
list($parts, $value) = explode(' = ', $s);
$parts = explode('.', $parts);
while($parts) {
$value = array(array_pop($parts) => $value);
}
print_r($parts);
Definitely rewrite it so it has error checking.
Gumbo's answer looks good.
However, it looks like you want to parse a typical .ini file.
Consider using library code instead of rolling your own.
For instance, Zend_Config handles this kind of thing nicely.
I really like JasonWolf answer to this.
As to the possible errors: yes, but he supplied a great idea, now it is up to the reader to make it bullet proof.
My need was a bit more basic: from a delimited list, create a MD array. I slightly modified his code to give me just that. This version will give you an array with or without a define string or even a string without the delimiter.
I hope someone can make this even better.
$parts = "config.debug.router.strictMode";
$parts = explode(".", $parts);
$value = null;
while($parts) {
$value = array(array_pop($parts) => $value);
}
print_r($value);
// The attribute to the right of the equals sign
$rightOfEquals = true;
$leftOfEquals = "config.debug.router.strictMode";
// Array of identifiers
$identifiers = explode(".", $leftOfEquals);
// How many 'identifiers' we have
$numIdentifiers = count($identifiers);
// Iterate through each identifier backwards
// We do this backwards because we want the "innermost" array element
// to be defined first.
for ($i = ($numIdentifiers - 1); $i >=0; $i--)
{
// If we are looking at the "last" identifier, then we know what its
// value is. It is the thing directly to the right of the equals sign.
if ($i == ($numIdentifiers - 1))
{
$a = array($identifiers[$i] => $rightOfEquals);
}
// Otherwise, we recursively append our new attribute to the beginning of the array.
else
{
$a = array($identifiers[$i] => $a);
}
}
print_r($a);