I have a particularly large graph, making it nearly impossible to traverse using recursion because of the excessive amount of memory it uses.
Below is my depth-first function, using recursion:
public function find_all_paths($start, $path)
{
$path[] = $start;
if (count($path)==25) /* Only want a path of maximum 25 vertices*/ {
$this->stacks[] = $path;
return $path;
}
$paths = array();
for($i = 0; $i < count($this->graph[$start])-1; $i++) {
if (!in_array($this->graph[$start][$i], $path)) {
$paths[] = $this->find_all_paths($this->graph[$start][$i], $path);
}
}
return $paths;
}
I would like to rewrite this function so it is non-recursive. I assume I will need to make a queue of some sort, and pop off values using array_shift() but in which part of the function, and how do I make sure the queued vertices are preserved (to put the final pathway on $this->stacks)?
It doesn't take exponential space, number of paths in a tree is equal to number of leaves, every leaf has only 1 path from the root ..
Here is a DFS simple search for an arbitrary binary tree:
// DFS: Parent-Left-Right
public function dfs_search ( $head, $key )
{
var $stack = array($head);
var $solution = array();
while (count($stack) > 0)
{
$node = array_pop($stack);
if ($node.val == $key)
{
$solution[] = $node;
}
if ($node.left != null)
{
array_push($stack, $node.left);
}
if ($node.right != null)
{
array_push($stack, $node.right);
}
}
return $solution;
}
What you need to find all paths in a tree is simply Branch & Fork, meaning whenever you branch, each branch takes a copy of the current path .. here is a 1-line recursive branch & fork I wrote:
// Branch & Fork
public function dfs_branchFork ( $node, $path )
{
return array($path)
+($node.right!=null?dfs_branchFork($node.right, $path+array($node)):null)
+($node.left!=null?dfs_branchFork($node.left, $path+array($node)):null);
}
Related
I wrote this code a long time ago to get files from a folder structure given in $dir.
$recursiveIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::CHILD_FIRST);
$ritit = new RegexIterator($recursiveIterator, $filter);
foreach ($ritit as $splFileInfo) {
if(($splFileInfo->getFileName() != ".") && ($splFileInfo->getFileName() != "..")) {
$path = $splFileInfo->isDir()
? array($splFileInfo->getFilename() => array())
: array($splFileInfo->getFilename());
for ($depth = $ritit->getDepth() - 1; $depth >= 0; $depth--) {
$path = array($ritit->getSubIterator($depth)->current()->getFilename() => $path);
}
$return = array_merge_recursive($return, $path);
}
}
And as the title suggests, I want the $return array to have the folders first. I first attempted to correct this with a foreach after the loop, and sort into $folders and $files array, however this wouldnt change the contents inside the folders, if there were mutliple children inside children.
Is there a way to modify the above loop so that all folders appear first in the array and files after? Including children and children's children?
I don't think that you can modify the loop to get the output array the way you want it. Instead, I'd rather use recursive sorting function to sort the array after the loop.
First, create function that defines the logic for sorting elements. In your case, you want the array-type elements to be the first elements in a tier, so the sorting function could look like this:
function dirFirstSorting($a, $b)
{
if (is_array($a) && is_string($b)) {
return -1;
} elseif (is_string($a) && is_array($b)) {
return 1;
} else {
return 0;
}
}
Then, create a function that recursively sorts elements in array:
function sortFilesListing(&$array)
{
if (is_array($array)) {
uasort($array, "dirFirstSorting");
array_walk($array, "sortFilesListing");
}
}
All you need to do now, is to call sortFilesListing function with $return array provided:
sortFilesListing($return);
The $return array elements should be now sorted accordingly.
I search a director with glob function and get the matched files' list. Then by checking filemtime of the files I create a map. Then sort the map with respect to file dates. At last I get latest file's name and modification time. My code is like this. It works well for small directories, but it's slow for big directories. I wonder whether is there a faster/clever way?
$fileList = array();
// $id = is the argument of this function
$files = glob('myfolder'.DS.'someone'.$id.'*.txt');
if (empty($files)) {
return 0;
}
foreach ($files as $file) {
$fileList[filemtime($file)] = $file;
}
if (sizeof($files) > 1) {
ksort($fileList);
}
$latestFilename = end($fileList);
$fileLastModifDate = filemtime( $latestFilename );
i suspect that your "mapping" is actually creating a hash table, and then the sort is not efficiant as one might expect, i would try this: (this is the basic, u can fancy it up)
class file_data
{
public $time; // or whatever u use
public $file;
}
$arr =new array ();
foreach ($files as $file) {
$new_file = new file_data ();
$file_data->time = filetime($file);
$file_data->file = $file;
array_push ($arr,$file_data);
}
function file_object_compare ($a,$b)
{
if ($a->time > $b->time) return -1;
// etc... i.e 0 eual 1 otherwise
}
//and then u sort
usort ($arr,"file_object_compare");
// or
// and this is probably better, if u only need this paricular sorting
function file_object_compare ($a,$b)
{
if (filetime($a)> filetime($b)) return -1;
// etc... i.e 0 eual 1 otherwise
}
usort ($files,"file_object_compare");
For example, I have some cached items with same prefix, such as
'app_111111', 'app_222222', 'app_333333', ...
Can I remove such 'app_xxxxxx' items by any memcached commands?
Memcached does not offer this functionality out of the box so you have to build it in yourself.
The way I solve this is by defining a prefix (or namespace) in my application for groups of keys. Any key that I set in memcached has that prefix before it. Whenever I want to "delete" stuff from Memcached, I just change the prefix. And whenever I want to lookup a key in Memcached, I add that prefix to it.
In your case, you could start by setting the prefix to, say, MyAppPrefix1, so your keys will be stored as MyAppPrefix1::app_333333, MyAppPrefix1::app_444444.
Later on when you want to "delete" these entries, set your application to use MyAppPrefix2. Then, when you try to get a key from Memcached called app_333333, it will look for MyAppPrefix2::app_333333 and will not find it the first time around, as if it had been deleted.
How about this function in php:
function deletekeysbyindex($prefix) {
$m = new Memcached();
$m->addServer('localhost', 11211);
$keys = $m->getAllKeys();
foreach ($keys as $index => $key) {
if (strpos($key,$prefix) !== 0) {
unset($keys[$index]);
} else {
$m->delete($key);
}
}
return $keys;
}
Deletes keys beginning with $prefix and returns a list of all keys removed. I ran this on 30,000+ keys just now on a shared server and it was pretty quick - probably less than one second.
This is a hack that works, albeit a bit slow. On a server with 0.6 million keys, it took half a second to complete.
$prefix = 'MyApp::Test';
$len = strlen($prefix);
$proc = popen('/usr/local/bin/memdump --servers=localhost', 'r');
while (($key = fgets($proc)) !== false) {
if (substr_compare($key, $prefix, 0, $len) === 0) {
$memcached->delete(substr($key, 0, -1));
}
}
We can not do that in only one request to memcache. We just can do this:
public function clearByPrefix($prefixes = array()) {
$prefixes = array_unique($prefixes);
$slabs = $this->memcache->getExtendedStats('slabs');
foreach ($slabs as $serverSlabs) {
if ($serverSlabs) {
foreach ($serverSlabs as $slabId => $slabMeta) {
if (is_int($slabId)) {
try {
$cacheDump = $this->memcache->getExtendedStats('cachedump', (int) $slabId, 1000);
} catch (Exception $e) {
continue;
}
if (is_array($cacheDump)) {
foreach ($cacheDump as $dump) {
if (is_array($dump)) {
foreach ($dump as $key => $value) {
$clearFlag = false;
// Check key has prefix or not
foreach ($prefixes as $prefix) {
$clearFlag = $clearFlag || preg_match('/^' . preg_quote($prefix, '/') . '/', $key);
}
// Clear cache
if ($clearFlag) {
$this->clear($key);
}
}
}
}
}
}
}
}
}
}
And call this function like this:
$prefixes = array();
array_push($prefixes, 'prefix1_');
array_push($prefixes, 'prefix2_');
array_push($prefixes, 'prefix3_');
$this->clearByPrefix($prefixes);
I have to make a function that lists all subfolders into a folder. I have a no-file filter, but the function uses scandir() for listing. That makes the application very slow. Is there an alternative of scandir(), even a not native php function?
Thanks in advance!
You can use readdir which may be faster, something like this:
function readDirectory($Directory,$Recursive = true)
{
if(is_dir($Directory) === false)
{
return false;
}
try
{
$Resource = opendir($Directory);
$Found = array();
while(false !== ($Item = readdir($Resource)))
{
if($Item == "." || $Item == "..")
{
continue;
}
if($Recursive === true && is_dir($Item))
{
$Found[] = readDirectory($Directory . $Item);
}else
{
$Found[] = $Directory . $Item;
}
}
}catch(Exception $e)
{
return false;
}
return $Found;
}
May require some tweeking but this is essentially what scandir does, and it should be faster, if not please write an update as i would like to see if i can make a faster solution.
Another issue is if your reading a very large directory your filling an array up within the internal memory and that may be where your memory is going.
You could try and create a function that reads in offsets so that you can return 50 files at a time!
reading chunks of files at a time would be just as simple to use, would be like so:
$offset = 0;
while(false !== ($Batch = ReadFilesByOffset("/tmp",$offset)))
{
//Use $batch here which contains 50 or less files!
//Increment the offset:
$offset += 50;
}
Don't write your own. PHP has a Recursive Directory Iterator built specifically for this:
http://php.net/manual/en/class.recursivedirectoryiterator.php
As a rule of thumb (aka not 100% of the time), since it's implemented in straight C, anything you build in PHP is going to be slower.
I have a script which handles the naming of parent/child elements on another page. The format for the name is like E5-2-3 which represents the third child of the second child of the fifth element.
What I need to do is pass in the parent name to the function and return the name for the next child. This value would be the increment of the last child or 1 if it is the first child.
(I hope this makes some sense to someone)
The index array looks something like this:
1=>null
2=>null
3=>
1=>null
2=>null
3=>
1=>null
4=>null
5=>
1=>null
2=>
1=>null
2=>null
3=>null //the element I was talking about above
6=>
1=>null
7=>null
My Code so far is
$projectNumber = $_GET['project_number'];
#$parentNumber = $_GET['parent_number']; //suppressed as it may not be set
$query = mysql_query("SELECT e_numbers FROM project_management WHERE project_number = '$projectNumber'");
$resultArray = mysql_fetch_assoc($query);
$eNumbers = unserialize($resultArray['e_numbers']);
if (!is_array($eNumbers)&&!isset($parentNumber)){ //first e_number assigned
$eNumbers[1] = null; //cant possibly have children so null for now
$nextENumber = 'E1';
}else{
if (!isset($parentNumber)){
$nextNumber = count($eNumbers)+1;
$eNumbers[$nextNumber] = null; //cant possibly have children so null for now
$nextENumber = 'E'.$nextNumber;
}else{
$parentIndex = explode('-', str_replace('E', '', $parentNumber));
//$nextENumber = //assign $nextENumber the incremented e number
}
}
echo $nextENumber;
//(then goes on to update sql etc etc)
This is all fine but for the line where I need to get/assign deep numbers. I think this should be some kind of recursive function based on the $parentIndex and $eNumbers arrays, however I'm a bit out of my depth when it comes to recursion.
Any pointer in the right direction will be a great help.
PS
If there is a better way to handle incrementing parent/child relationships I'm all ears. The only thing out of my control is the format of the numbers being passed in/out (Has to be EX-Y-Z-...)
UPDATE I was able to develop #ircmaxell 's function to function more better in my context. The function required you to pass in a zero based array(can be empty) and an optional path. It returns the new path and updates the index array to include the new path. An error message is returned if the index is not found.
function getNextPath(&$array, $path) { //thanks to ircmaxell # stackoverflow for the basis of this function
$newPath = '';
$tmp =& $array;
if (is_string($path)) {
$path = explode('-', str_replace('E', '', $path));
$max = count($path);
foreach ($path as $key => $subpath) {
if (is_array($tmp)) {
if (array_key_exists($subpath, $tmp)){
$tmp =& $tmp[$subpath];
$newPath[] = $subpath;
}else{
return "Parent Path Not Found";
}
}
}
}
$tmp[] = null;
$newPath[] = count($tmp)-1;
if (count($newPath)>1){
$newPath = implode('-', $newPath);
}else{
$newPath = $newPath[0];
}
return "E".$newPath;
}
Here's one way:
function incrementPath(&$array, $path) {
if (is_string($path)) {
$path = explode('-', str_replace('E', '', $path);
}
$tmp =& $array;
foreach ($path as $subpath) {
if (is_array($tmp) && isset($tmp[$subpath])) {
$tmp =& $tmp[$subpath];
} else {
return false; // Could not find entire path
}
}
$tmp++;
return true;
}
Now, if you want it to dynamically create paths, just change the return false; to:
$tmp[$subpath] = array();
$tmp =& $tmp[$subpath];
And then add a check after the loop to see if it's not an integer, and explicitly set to 0 if it isn't...
Edit: AHHH, now I understand:
function getNextPath(&$array, $path) {
if (is_string($path)) {
$path = explode('-', str_replace('E', '', $path);
}
$newPath = '';
$tmp =& $array;
$max = count($path) - 1;
foreach ($path as $key => $subpath) {
if (is_array($tmp) && isset($tmp[$subpath])) {
$tmp =& $tmp[$subpath];
if ($key < $max) {
$newPath .= '-'.$subpath;
}
} else {
return 'E' . ltrim($newPath . '-1', '-'); // Could not find entire path
}
}
if (is_array($tmp)) {
return 'E' . ltrim($newPath . '-' . count($tmp), '-');
} else {
//it's a value, so make it an array
$tmp = array();
return 'E' . ltrim($newPath . '-' . 1, '-');
}
}
I think that should do what you want (it returns the next available path under what you're looking for).
}