Используя PHP, как использовать рекурсивное программирование и mkdir () для создания этой иерархии? - PullRequest
0 голосов
/ 17 сентября 2011

Я хочу создать структуру каталогов на основе карты разделов, как в этих примерах.Я пробовал разные подходы с провалом.Из-за рекурсивного программирования у меня болит мозг!

Пример 1

$partitionMap = '#/#'; //An example of two levels deep

myfolder/0
myfolder/1
myfolder/2  
...
myfolder/f
myfolder/f/0
myfolder/f/1
myfolder/f/2
myfolder/f/3
...

Пример 2

$partitionMap = '#/#/#'; //An example of three levels deep

myfolder/a
myfolder/a/4
myfolder/a/4/f

Пример 3

$partitionMap = '#/##/###'; //An example of three levels deep

myfolder/0
myfolder/1
myfolder/2
...
myfolder/a/00
myfolder/a/01
myfolder/e/02
myfolder/e/03
...
myfolder/e/03/af0
myfolder/e/03/af1
myfolder/e/03/af2
myfolder/e/03/af3

шестнадцатеричный заполнитель, представляющий 0-F, как вы можете видеть выше.

Так что мне нужно что-то, что работает следующим образом ..

$partitionMap = '#/##'; 

makeFoldersRecursive( './myfolder', $partitionMap );

function makeFoldersRecursive( $path, $pmap ){
    $pmapArr = explode( '/', $pmap );

    $numNibbles = strlen( $pmapArr[0] );
    //Get hex folder count but in dec, # = 16, ## = 256, ### = 4096
    $folder_count_this_level = pow( 16, $numNibbles );

    for($i=0; $i<$count; $i++ ) {
        //Get left padded hex folder name from $i
        $folder = sprintf("%0" . $numNibbles . "s", dechex($i));
        mkdir( $path .  DIRECTORY_SEPARATOR . $folder , 0755 );

        if( array_key_exists( 1, $pmapArr ) ) {
            $passMap = $pmapArr; 
            array_shift( $pmapArr );
            makeFoldersRecursive( $path .  DIRECTORY_SEPARATOR . $folder, $passMap );
        }
    }



}

Должен быть более элегантный способ сделать это, любойбыла бы признательна за помощь!

Ответы [ 4 ]

1 голос
/ 18 сентября 2011

Чистое рекурсивное решение

У вас уже есть один, но я сделаю еще один поворот, который выглядит "чище" для моих глаз, а также объясню, как я туда попал.

Вот код:

function create_all_dirs($directory, $map) {
    // 1: TERMINATION CONDITION
    if (empty($map)) {
        mkdir($directory, 0755, true);
        return;
    }

    // 2: PROCESS PART OF DATA, PREPARE DATA TO PASS ON RECURSION
    $parts = explode('/', $map);
    $partLength = strlen(array_shift($parts));
    $dirCount = pow(16, $partLength);    
    $map = implode('/', $parts);

    // 3: RECURSE
    for($i = 0; $i < $dirCount; ++$i) {
        $dir = $directory.DIRECTORY_SEPARATOR
                         .sprintf('%0'.$partLength.'s', dechex($i));
        create_all_dirs($dir, $map);
    }
}

Объяснение

Хотя я даже не буду предполагать, что есть один хороший способ достижения решения для рекурсивных функций, я могу объяснить подход, который хорошо сработал для меня на практике.

Шаг 1: Подумайте, какие данные вам нужно будет передать при повторении.

В этом случае каждый уровень функции будет отвечать за создание одного уровня структуры каталогов. Так что имеет смысл, что если вы позвоните

create_all_dirs('c:/', '#/#')

тогда в какой-то момент функция сделает рекурсивный вызов, который выглядит следующим образом:

create_all_dirs('c:/0', '#')

Это все, что вам нужно определить на этом этапе. Вы можете сказать, что это имеет смысл и что после последовательности таких вызовов функция будет (каким-то образом, мы ее еще не написали) выполнить свою работу.

Шаг 2: Условие завершения

Обладая этими знаниями, определите условие завершения вашей функции. В нашем случае, учитывая вышеизложенное, разумным условием завершения будет «когда вторым параметром является пустая строка». Так что бы сделала функция, если бы она вызывалась с каталогом и пустой строкой «map»?

function create_all_dirs($directory, $map) {
    if(empty($map)) {
        mkdir($directory, 0755, true);
        return;
    }

    // ???
}

Очевидно, это должно создать каталог. Я звоню mkdir таким образом, что он будет рекурсивно создавать все каталоги на пути, если они еще не существуют, потому что окончательное решение должно работать, но это не обязательно. Вы уже знаете, как это сделать без этого удобства.

Шаг 3: Подготовьте параметры, общие для всех рекурсивных вызовов

ОК, так что нам нужно сделать рекурсивный вызов? Вы уже знаете, что на каждом уровне нам нужно вытолкнуть часть строки $map и несколько раз вызвать функцию рекурсивно. Нам также нужно передать ему новую, более глубокую строку каталога и новую, более короткую строку карты (все это вы можете узнать из завершения шага 1, даже если вы еще не создали решение).

Итак, мы имеем:

// cut off part of $map; we also need the length of the part we cut
// off, to know how many times we need to recursively call at this level
$parts = explode('/', $map);
$partLength = strlen(array_shift($parts));

// how many recursive calls?
$dirCount = pow(16, $partLength);

// this will be the $map parameter for all recursive calls
$map = implode('/', $parts);

Шаг 4. Вызов функции рекурсивно

На данный момент я не думаю, что есть что-то еще, чтобы объяснить. Мы вычисляем значение $directory, которое отличается для каждого рекурсивного вызова, и делаем его:

for($i = 0; $i < $dirCount; ++$i) {
    $dir = $directory.DIRECTORY_SEPARATOR
                     .sprintf('%0'.$partLength.'s', dechex($i));
    create_all_dirs($dir, $map);
}
0 голосов
/ 31 января 2013

Пока вам не нужны длинные имена каталогов (до 8 шестнадцатеричных цифр в 32-битных системах), вы можете использовать это (все три версии имеют постоянную сложность памяти):

// Example for "#/##"
for ($i = 0; $i <= 0x0FFF; $i++) {
    $d = sprintf("%01x/%02x", $i & 0x00F, ($i & 0xFF0) >> 4);
    echo $d, "\n";
    mkdir($d, 0777, true);
}

И с использованием строк вашего формата:

function mkhexdir($format) {
    // Parse format string
    $fmt_N = array_map('strlen', explode('/', $format));
    $bit_count = array_sum($fmt_N) * 4;
    $max = 1 << $bit_count;

    // Prepare format string for sprintf
    $fmt_printf = join('/', array_map(function($n) { return '%0'.$n.'x';}, $fmt_N));

    // Generate all dirs
    for ($i = 0; $i < $max; $i++) {
        $args = array();
        $offset = $bit_count;
        foreach ($fmt_N as $len) {
            $offset -= $len * 4;
            $b = (1 << ($len * 4)) - 1;
            $args[] = ($i >> $offset) & $b;
        }
        $d = vsprintf($fmt_printf, $args);
        echo $d, "\n";
        mkdir($d, 0777, true);
    }
}

mkhexdir('#/##/##');

Или другая версия, которая использует регулярное выражение:

function mkhexdir($format) {
    // Parse format string
    $fmt_N = array_map('strlen', explode('/', $format));
    $digits = array_sum($fmt_N);
    $max = 1 << ($digits * 4);

    // Prepare format string for sprintf
    $fmt_printf = '%0'.$digits.'x';

    // Prepare regular expression to format path
    $src = '/^('.join(')(',
            array_map(function($n){ return str_repeat('.', $n);}, $fmt_N))
            .')$/';
    $dst = '\\'.join('/\\',
            array_map(function($n){ return $n + 1;}, array_keys($fmt_N)));

    // Generate all dirs
    for ($i = 0; $i < $max; $i++) {
        $d = preg_replace($src, $dst, sprintf($fmt_printf, $i));
        echo $d, "\n";
    }
}

mkhexdir('#/##/##');
0 голосов
/ 31 января 2013

Я нашел это где-то в интернете. кажется, работает, если вы не используете относительный путь. т.е. "../basepath/newpath1/newpath2". Все еще пытаюсь понять это.

/**
 * recursively create a long directory path
 */
function createPath($path, $rwd=0777) {
    if (is_dir($path)) return true;
    rev_path = substr($path, 0, strrpos($path, '/', -2) + 1 );
$return = createPath($prev_path);
    return ($return && is_writable($prev_path)) ? mkdir($path, $rwd, true) : false;
}

Надеюсь, это кому-нибудь поможет. Я думаю, что это довольно элегантно. Просто комментарий, рекурсия является очень распространенной практикой во всех языках программирования и имеет свои места. Попытайтесь понять это, не принижайте это только потому, что вы отказываетесь учиться кодировать это. Например, попробуйте получить список файлов без рекурсии и функций, и вы очень быстро потеряете себя.

0 голосов
/ 18 сентября 2011

Рекурсия на самом деле не нужна и только делает вещи более запутанными. Это решение использует очередь напрямую, чтобы быть более простым.

Итак, предполагая, что у вас есть шаблон типа '# / #', вы помещаете его в очередь. Затем вы снимаете его и заменяете первый знак «#» на него одним из ваших персонажей. Это эффективно умножает количество элементов в очереди на 16. Вы повторяете этот процесс, снимая элемент, выполняя замену и помещая его обратно, пока не замените все «#» в каждом элементе в очереди.

Тогда вы можете просто создать каталоги как обычно.

$partitionMap = '#/##';

makeFolders('./myfolder', $partitionMap);

function makeFolders($path, $pmap) {

  // Use number of characters and number of '#' to compute total combinations
  $characters = array(
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
  );
  $count = substr_count($pmap, '#');
  $num_results = pow(count($characters), $count);

  // Start array with path and pmap
  $combos = array( $path . '/' . $pmap );

  // Go through array, replacing each '#' with one of the characters.
  while (count($combos) < $num_results) {
    $str = array_shift($combos);

    foreach ($characters as $c) {
      array_push($combos, preg_replace('/#/', $c, $str, 1));
    }
  }

  // Create the directories, using third 'TRUE' parameter
  // to avoid creating parents manually
  foreach ($combos as $dir) {
    mkdir($dir, 0755, TRUE);
  }
}
...