преобразовать разделенные табуляцией / пробелом строки во вложенный массив - PullRequest
3 голосов
/ 21 марта 2012

Я хотел бы преобразовать приведенный ниже текст во вложенный массив, что-то похожее на структуру базы данных MPTT.

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

Существует много информации о массиве -> списке, но не так много иного.

Любой вклад будет оценен, спасибо.

cat, true cat
       => domestic cat, house cat, Felis domesticus, Felis catus
           => kitty, kitty-cat, puss
           => mouser
           => alley cat
           => tom, tomcat
               => gib
           => Angora, Angora cat
           => Siamese cat, Siamese
               => blue point Siamese
       => wildcat
           => sand cat
           => European wildcat, catamountain, Felis silvestris
           => cougar, puma, catamount, mountain lion, painter, panther, Felis concolor
           => ocelot, panther cat, Felis pardalis
           => manul, Pallas's cat, Felis manul
           => lynx, catamount
               => common lynx, Lynx lynx
               => Canada lynx, Lynx canadensis

Ответы [ 2 ]

5 голосов
/ 29 июня 2012

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

1 Line <=> 1 Element (level, name)

Таким образом, каждый элемент имеет имя и ноль.или больше детей.Из входных данных также можно сказать, к какому уровню он принадлежит.

Элемент может быть представлен в виде массива, в котором его первое значение является именем, а второе значение является массивом для дочерних элементов.

Поскольку список отсортирован, мы можем использовать простую карту, которая для каждого уровня является псевдонимом для детей определенного уровня.Таким образом, с уровнем, который имеет каждый элемент, мы можем добавить его в стек:

    $self = array($element, array());
    $stack[$level][] = &$self;
    $stack[$level + 1] = &$self[1];

Как показывает этот пример кода, стек / карта для текущего уровня получает $self как добавленные дочерние элементы:

    $stack[$level][] = &$self;

Стек для первого уровня, получить ссылку на потомков $self (index 1):

    $stack[$level + 1] = &$self[1];

Итак, теперь для каждой строки нам нужнонайти уровень.Как показывает этот стек, уровень последовательно нумеруется: 0, 1, 2, ..., но во входных данных это просто количество пробелов.

Маленький вспомогательный объект может выполнять работу по сбору / группировке количества символов в строкек уровням, следя за тем, чтобы - если уровень еще не существует для отступа - он добавляется, но только если он выше.

Это решает проблему, заключающуюся в том, что в ваших входных данных нет отношения 1: 1 междуразмер отступа и его индекс.По крайней мере, не очевидный.

Этот вспомогательный объект является образцовым именем Levels и реализует __invoke, чтобы обеспечить уровень для отступа при прозрачном добавлении нового уровня при необходимости:

$levels = new Levels();
echo $levels(''); # 0
echo $levels('    '); # 1
echo $levels('    '); # 1
echo $levels('      '); # 2
echo $levels(' '); # Throws Exception, this is smaller than the highest one

Так что теперь мы можем превратить отступ в уровень.Этот уровень позволяет нам запускать стек.Стек позволяет строить дерево.Fine.

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

$matches = function($string, $pattern)
{
    return preg_match_all($pattern, $string, $matches, PREG_SET_ORDER)
        ? $matches : array();
};

Использование при вводе с шаблоном, подобным

/^(?:(\s*)=> )?(.*)$/m

даст мне массив для каждой строки, то есть:

array(whole_line, indent, name)

Вы видите образец здесь?Это близко к

1 Line <=> 1 Element (level, name)

С помощью объекта Levels это может быть отображено, поэтому просто вызов функции отображения:

function (array $match) use ($levels) {
    list(, $indent, $name) = $match;
    $level = $levels($indent);
    return array($level, $name);
};

От array(line, indent, name) до array(level, name).Чтобы сделать это доступным, его возвращает другая функция, в которую можно ввести Levels:

$map = function(Levels $levels) {
    return function ...
};
$map = $map(new Levels());

Итак, все для чтения со всех строк.Тем не менее, это должно быть помещено в дерево.Запоминание добавления в стек:

function($level, $element) use (&$stack) {
    $self = array($element, array());
    $stack[$level][] = &$self;
    $stack[$level + 1] = &$self[1];
};

($element - это имя здесь).Для этого фактически нужен стек, а стек - это дерево.Итак, давайте создадим другую функцию, которая возвращает эту функцию и позволяет помещать каждую строку в стек для построения дерева:

$tree = array();
$stack = function(array &$tree) {
    $stack[] = &$tree;
    return function($level, $element) use (&$stack) {
        $self = array($element, array());
        $stack[$level][] = &$self;
        $stack[$level + 1] = &$self[1];
    };
};
$push = $stack($tree);

Итак, последнее, что нужно сделать, это просто обработать один элемент за другим:

foreach ($matches($input, '/^(?:(\s*)=> )?(.*)$/m') as $match) {
    list($level, $element) = $map($match);
    $push($level, $element);
}

Итак, теперь с учетом $input создается массив с только (корневыми) дочерними узлами на первом уровне, а затем array с двумя записями на каждый узел:

array(name, children)

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

Позвольте мне привести рекурсивный итератор Пример:

class TreeIterator extends ArrayIterator implements RecursiveIterator
{
    private $current;

    public function __construct($node)
    {
        parent::__construct($node);
    }

    public function current()
    {
        $this->current = parent::current();
        return $this->current[0];
    }

    public function hasChildren()
    {
        return !empty($this->current[1]);
    }

    public function getChildren()
    {
        return new self($this->current[1]);
    }
}

Это просто итератор массива (как и все узлыявляются массивом, а также все дочерние узлы) и для текущего узла он возвращает имя.Если спросить детей, он проверяет, есть ли они, и снова предлагает их как TreeIterator.Это упрощает использование, например, вывод в виде текста:

$treeIterator = new RecursiveTreeIterator(
    new TreeIterator($tree));

foreach ($treeIterator as $val) echo $val, "\n";

Вывод:

\-cat, true cat
  |-domestic cat, house cat, Felis domesticus, Felis catus
  | |-kitty, kitty-cat, puss
  | |-mouser
  | |-alley cat
  | |-tom, tomcat
  | | \-gib
  | |-Angora, Angora cat
  | \-Siamese cat, Siamese
  |   \-blue point Siamese
  \-wildcat
    |-sand cat
    |-European wildcat, catamountain, Felis silvestris
    |-cougar, puma, catamount, mountain lion, painter, panther, Felis concolor
    |-ocelot, panther cat, Felis pardalis
    |-manul, Pallas's cat, Felis manul
    \-lynx, catamount
      |-common lynx, Lynx lynx
      \-Canada lynx, Lynx canadensis

Если вы ищете дополнительные элементы управления выводом HTML в сочетании с рекурсивным итератором, см. Следующий вопрос, содержащий пример вывода HTML на основе <ul><li>:

Так как это выглядит все вместе? Код для просмотра сразу как гист на github .

3 голосов
/ 30 июня 2012

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

  • Разделение строк может быть выполнено с помощью strtok
  • preg_match, затем "на" строке, что делает отображение более имманентным
  • . Levels может быть сжат в массив, который считается само собой разумеющимся, что ввод правильный.

На этот раз для вывода это рекурсивная функция, а не итератор, которая выдает вложенный список <ul>.Пример кода ( Демонстрация ):

// build tree
$tree = $levels = array();
$stack[1] = &$tree;
for ($line = strtok($input, $token = "\n"); $line; $line = strtok($token)) {
    if (!preg_match('/^(?:(\s*)=> )?(.*)$/', $line, $self)) {
        continue;
    }
    array_shift($self);
    $indent = array_shift($self);
    $level = @$levels[$indent] ? : $levels[$indent] = count($levels) + 1;
    $stack[$level][] = &$self;
    $stack[$level + 1] = &$self[];
    unset($self);
}
unset($stack);

// output
tree_print($tree);
function tree_print(array $tree, $in = '') {
    echo "$in<ul>\n";
    $i = $in . '  ';
    foreach ($tree as $n)
        printf("</li>\n", printf("$i<li>$n[0]") && $n[1] && printf($i, printf("\n") & tree_print($n[1], "$i  ")));

    echo "$in</ul>\n";
}

Редактировать: Ниже приведен еще один шаг, чтобы полностью отбросить деревомассив и сделать вывод напрямую.Это немного безумно, потому что смешивает переупорядочение данных и вывода, что объединяет вещи, так что их нелегко изменить.Также предыдущий пример уже выглядит загадочно, это за гранью добра и зла ( Demo ):

echo_list($input);

function echo_list($string) {
    foreach ($m = array_map(function($v) use (&$l) {
        return array(@$l[$i = &$v[1]] ? : $l[$i] = count($l) + 1, $v[2]);
    }, preg_match_all('/^(?:(\s*)=> )?(.*)$/m', $string, $m, PREG_SET_ORDER) ? $m : array()) as $i => $v) {
        $pi = str_repeat("    ", $pl = @$m[$i - 1][0]); # prev
        $ni = str_repeat("    ", $nl = @$m[$i + 1][0]); # next
        (($v[0] - $pl) > 0) && printf("$pi<ul>\n");     # is child of prev
        echo '  ' . str_repeat("    ", $v[0] - 1), "<li>$v[1]"; # output self
        if (!$close = (($nl - $v[0]) * -1)) echo "</li>\n"; # has sibling
        else if ($close < 0) echo "\n";                     # has children
        else for (printf("</li>\n$ni" . str_repeat("    ", $close - 1) . "</ul>\n"); --$close;) # is last child
                echo $ni, $nn = str_repeat("    ", $close - 1), "  </li>\n",
                     $ni, $nn, "</ul>\n";
    }
}

Это снова отбрасывает strtok и возвращает к идее использовать preg_match_all,Кроме того, в нем хранятся все проанализированные строки, так что можно посмотреть назад и вперед, чтобы определить, сколько элементов <ul> необходимо открыть или закрыть вокруг текущего элемента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...