Создайте файл иерархии, используя perl - PullRequest
5 голосов
/ 05 октября 2010

Моя задача - создать файл иерархии родитель-потомок с помощью perl.

Пример входного файла (с разделителями табуляции). Записи будут расположены в файле в случайном порядке, и после «потомка» может появиться «родитель».

 S5 S3
 S5 S8
 ROOT   S1
 S1 S7
 S2 S5
 S3 S4
 S1 S2
 S4 77
 S2 S9
 S3 88

Пример выходного файла (с разделителями табуляции)

ROOT    S1  S2  S5  S3  S4  77
ROOT    S1  S2  S5  S3  88
ROOT    S1  S7
ROOT    S1  S2  S5  S8
ROOT    S1  S2  S9

Код, который производит вышеуказанный выходной файл

use strict;

# usage: perl parent_child_generator.pl input.txt output.txt

my $input0=$ARGV[0] or die "must provide input.txt as the first argument\n";
my $output1=$ARGV[1] or die "must provide output.txt as the second argument\n";

open(IN0,"<",$input0) || die "Cannot open $input0 for reading: $!";
open(OUT1,">",$output1) || die "Cannot open $output1 for writing: $!";

sub trim
{
    my $string=shift;
$string=~s/\r?\n$//;
$string=~s/^\s+//;
$string=~s/\s+$//;
return $string;
}

sub connectByPrior
{
my $in_child=$_[0];
my %in_hash=%{$_[1]};
my @anscestor_arr;

for (sort keys %in_hash)
{
    my $key=$_;
    my @key_arr=split(/\t/,$key);
    my $parent=$key_arr[0];
    my $child=$key_arr[1];

    if ($in_child eq $child)
    {
        push (@anscestor_arr,$parent);
        @anscestor_arr=(@{connectByPrior($parent,\%in_hash)},@anscestor_arr);
        last;
    }
}
return \@anscestor_arr;
}

my %parent_hash;
my %child_hash;
my %unsorted_hash;
while(<IN0>)
{
my @cols=split(/\t/);
for (my $i=0; $i < scalar(@cols); $i++)
{
    $cols[$i]= trim($cols[$i]);
}

my $parent=$cols[0];
my $child=$cols[1];
my $parent_child="$parent\t$child";

$parent_hash{$parent}=1;
$child_hash{$child}=1;
$unsorted_hash{$parent_child}=1;
 }
 close(IN0);

my @lev0_arr;
for (sort keys %child_hash)
{
my $rec=$_;
if (!exists($parent_hash{$rec}))
{
    push (@lev0_arr,$rec);
}
}

for (@lev0_arr)
{
my $child=$_;
my @anscestor_arr=@{connectByPrior($child,\%unsorted_hash)};
push (@anscestor_arr,$child);
print OUT1 join("\t",@anscestor_arr)."\n";
}

Вопрос: код работает нормально, если входной файл не слишком большой. Фактический входной файл содержит более 200 тыс. Строк, и на обработку кода уходит слишком много времени. Какие улучшения / изменения вы предлагаете, чтобы обработка не заняла слишком много времени?

Ответы [ 3 ]

6 голосов
/ 05 октября 2010

Вы, кажется, пытаетесь построить и красиво напечатать ориентированный граф:

#!/usr/bin/perl

use strict; use warnings;
use Graph::Directed;
use Graph::TransitiveClosure::Matrix;

 my $g = Graph::Directed->new;

while ( my $line = <DATA> ) {
    next unless my ($x, $y) = split ' ', $line;
    $g->add_edge($x, $y);
}

my @start = $g->source_vertices;
my @end   = $g->sink_vertices;

my $tcm = Graph::TransitiveClosure::Matrix->new( $g,
    path_vertices => 1,
);

for my $s ( @start ) {
    for my $e ( @end ) {
        next unless $tcm->is_reachable($s, $e);
        print join("\t", $tcm->path_vertices($s, $e)), "\n";
    }
}

__DATA__
S5 S3
S5 S8
ROOT   S1
S1 S7
S2 S5
S3 S4
S1 S2
S4 77
S2 S9
S3 88

Выход:

ROOT    S1      S2      S9
ROOT    S1      S2      S5      S8
ROOT    S1      S2      S5      S3      S4      77
ROOT    S1      S2      S5      S3      88
ROOT    S1      S7

Я не уверен, что использование памяти График и вычисление матрицы транзитивного замыкания в вашем случае будет чрезмерным.

5 голосов
/ 05 октября 2010

Первое, что приходит на ум, хотя это совершенно не связано с вашим реальным вопросом, это то, что вы должны хотя бы рассмотреть возможность сделать свой интерфейс более гибким.Если вы прочитаете из <> и напечатаете в STDOUT, вы сможете получить ввод либо из STDIN, либо из списка файлов произвольного размера, указанного в командной строке, а вывод можно просмотреть на консоли.или перенаправлен в файл с незначительным изменением в соглашении о вызовах:

parent_child_generator.pl input1.txt input2.txt input3.txt > output.txt

Еще один незначительный момент заключается в том, что $string=~s/\r?\n$//; в trim не требуется [1].$string=~s/\s+$//; позаботится об этом:

$ perl -e 'my $foo = "test\r\n"; print "--$foo--\n"; $foo =~ s/\s+$//; print "--$foo--\n";'
--test
--
--test--

Что касается вашей проблемы с производительностью (наконец ...), основная проблема заключается в том, что вы вызываете connectByPrior для каждого элемента в @lev0_arr [2] и connectByPrior не только зацикливается на %unsorted_hash [3] при каждом вызове, но и внутри этого цикла вызывает себя рекурсивно!В первом приближении это где-то между O (n ^ 2 log n) и O (n ^ 3), в зависимости от формы ваших деревьев, что просто ужасно.Вы должны избегать касания каждого фрагмента данных несколько раз для каждого другого фрагмента данных, которые вы получаете.

Итак, как бы я поступил так?Моя первая мысль - использовать один хеш для отслеживания моих корневых узлов (все те, которые не имеют к ним никаких ссылок) и один хеш (HoH) для отслеживания всех ссылок.Когда каждая строка ввода видна, разделите ее на родительский и дочерний, как вы делаете.Если у родителя еще нет записи в ссылке HoH, добавьте ее в корневой хэш.Если ребенок находится в корневом хеше, удалите его.Если дочерний элемент отсутствует в ссылках HoH, добавьте для него пустой хэш-код (так что в будущем мы узнаем, что это не root).Наконец, добавьте в ссылку HoH запись, указывающую, что родительский элемент ссылается на дочерний элемент.

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

Примерно так:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

my %root;
my %link;

while (<>) {
  my ($parent, $child) = split /\t/, $_, 2;
  next unless defined $parent and defined $child;
  $_ = trim($_) for ($parent, $child);

  $root{$parent} = 1 unless exists $link{$parent};
  delete $root{$child};
  $link{$child} ||= {};
  $link{$parent}{$child} = 1;
}

print_links($_) for sort keys %root;

exit;

sub trim {
  my $string=shift;
  $string=~s/^\s+//;
  $string=~s/\s+$//;
  return $string;
}

sub print_links {
  my @path = @_;

  my %children = %{$link{$path[-1]}};
  if (%children) {
    print_links(@path, $_) for sort keys %children;
  } else {
    say join "\t", @path;
  }
}

Исходя из вашего примера ввода, вы получите вывод:

ROOT    S1      S2      S5      S3      88
ROOT    S1      S2      S5      S3      S4      77
ROOT    S1      S2      S5      S8
ROOT    S1      S2      S9
ROOT    S1      S7

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

(Конечно, Синан прав, если вы хотите перейти на CPAN, если вы 'я действительно хочу добиться своей цели, но мне было весело с этим.)

Редактировать: Код должен проверить, определены ли $parent и $child, а не являются ли ониtrue, согласно комментарию Синан.

[1] Обычно вы должны использовать chomp, чтобы удалить переносы строк вместо регулярных выражений, но я даю вам преимущество сомнения и предполагаю, что вы, возможно, обрабатываете вводсодержащий один стиль разрывов строк в среде, которая использует другой стиль.

[2] ... который содержит все конечные узлы, поэтому он будет довольно большим с 200k входными строками, если у вас нет слишком узких, глубокие деревья.

[3] ... который содержит каждую строку ввода, обрезанную от посторонних пробелов.

0 голосов
/ 06 октября 2010

Если вы знаете, как называется ваш ROOT узел, то, возможно, даже третий путь :)

use 5.012;
use warnings;

my $twigs = build_twigs_from( *DATA );
recurse_print( 'ROOT', $twigs->{ROOT} );    # explicit 

sub build_twigs_from {
    my $fh = shift;
    my %twigs;

    while (<$fh>) {
        $twigs{ $+{parent} }->{ $+{child}  } = $twigs{ $+{child} } //= {}
            if m/ (?<parent> \S+) \s+ (?<child> \S+) /x;
    }

    return \%twigs;
}

sub recurse_print {
    my ($path, $child) = @_;

    # reached end of twig?
    unless (%$child) {
        say $path;
        return;
    }

    recurse_print( $path . "\t$_", $child->{$_} )
        for sort keys %$child;
}

__DATA__
S5 S3
S5 S8
ROOT   S1
S1 S7
S2 S5
S3 S4
S1 S2
S4 77
S2 S9
S3 88

Выше используются ссылки (в данном случае ссылка на хеш, но это также может быть ссылка на массив или смесь), чтобы связать (то есть псевдоним) все узлы (ветки) вместе. Это позволяет ему держать ключ плоским (в $ twigs hashref) для каждой ячейки , и поэтому каждый узел просто ссылается на это.

Если вы наберете Data::Dumper из $twigs, вы увидите это:

   $VAR1 =
   {
      'S1' => {
                'S2' => {
                          'S5' => {
                                    'S8' => {},
                                    'S3' => {
                                              '88' => {},
                                              'S4' => {
                                                        '77' => {}
                                                      }
                                            }
                                  },
                          'S9' => {}
                        },
                'S7' => {}
              },
      'S9' => $VAR1->{'S1'}{'S2'}{'S9'},
      'S4' => $VAR1->{'S1'}{'S2'}{'S5'}{'S3'}{'S4'},
      'ROOT' => {
                  'S1' => $VAR1->{'S1'}
                },
      'S8' => $VAR1->{'S1'}{'S2'}{'S5'}{'S8'},
      '88' => $VAR1->{'S1'}{'S2'}{'S5'}{'S3'}{'88'},
      '77' => $VAR1->{'S1'}{'S2'}{'S5'}{'S3'}{'S4'}{'77'},
      'S2' => $VAR1->{'S1'}{'S2'},
      'S5' => $VAR1->{'S1'}{'S2'}{'S5'},
      'S7' => $VAR1->{'S1'}{'S7'},
      'S3' => $VAR1->{'S1'}{'S2'}{'S5'}{'S3'}
    };

Что может показаться немного абсурдным, но если вы перебираете ключ ROOT, тогда будет отображаться полная иерархия связанных ссылок на хеш (достигающих конца узла / ветки, когда дело доходит до пустого хэшрефа). *

Подпрограмма recurse_print() выводит следующий вывод из $twigs->{ROOT}:

ROOT    S1  S2  S5  S3  88
ROOT    S1  S2  S5  S3  S4  77
ROOT    S1  S2  S5  S8
ROOT    S1  S2  S9
ROOT    S1  S7

Надеюсь, я что-то не пропустил, и это работает с вашими данными в 200 КБ (мне было бы интересно узнать, насколько эффективно это решение).

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