Заказ массива по зависимостям с perl

Имеет массив хэшей,

my @arr = get_from_somewhere();

содержание @arr (например):

@arr = (
  { id => "id2",    requires => 'someid', text => "another text2" },
  { id => "xid4",   requires => 'id2',    text => "text44" },
  { id => "someid", requires => undef,    text => "some text" },
  { id => "id2",    requires => 'someid', text => "another text2" },
  { id => "aid",    requires => undef,    text => "alone text" },
  { id => "id2",    requires => 'someid', text => "another text2" },
  { id => "xid3",   requires => 'id2',    text => "text33" },
);

нужно что-то вроде:

my $texts = join("\n",  get_ordered_texts(@arr) );

soo нужно написать sub, который возвращает массив text из хэшей, - в зависимом порядке, поэтому из приведенного выше примера нужно получить:

"some text",     #someid the id2 depends on it - so need be before id2
"another text2", #id2    the xid3 and xid4 depends on it - and it is depends on someid
"text44",        #xid4   the xid4 and xid3 can be in any order, because nothing depend on them
"text33",        #xid3   but need be bellow id2
"alone text",    #aid    nothing depends on aid and hasn't any dependencies, so this line can be anywhere

как вы можете видеть, в @arr могут быть некоторые дублированные "строки" ( "id2" в приведенном выше примере), нужно выводить только один раз на любой идентификатор.

Пока не указывается какой-либо пример кода, потому что у вас нет идеи, как начать.; ( Существует какой-то модуль CPAN, который можно использовать для решения?

Может ли кто-нибудь указать мне в правильном направлении?

Ответ 1

Использование Graph:

use Graph qw( );

my @recs = (
   { id => "id2",    requires => 'someid', text => "another text2" },
   { id => "xid4",   requires => 'id2',    text => "text44" },
   { id => "someid", requires => undef,    text => "some text" },
   { id => "id2",    requires => 'someid', text => "another text2" },
   { id => "aid",    requires => undef,    text => "alone text" },
   { id => "id2",    requires => 'someid', text => "another text2" },
   { id => "xid3",   requires => 'id2',    text => "text33" },
);

sub get_ordered_recs {
   my %recs;
   my $graph = Graph->new();
   for my $rec (@_) {
      my ($id, $requires) = @{$rec}{qw( id requires )};

      $graph->add_vertex($id);
      $graph->add_edge($requires, $id) if $requires;

      $recs{$id} = $rec;
   }

   return map $recs{$_}, $graph->topological_sort();
}

my @texts = map $_->{text}, get_ordered_recs(@recs);

Ответ 2

Интересная проблема.

Здесь мое решение первого раунда:

sub get_ordered_texts {
    my %dep_found;  # track the set of known dependencies
    my @sorted_arr; # output

    my $last_count = scalar @_; # infinite loop protection
    while (@_ > 0) {
        for my $value (@_) {

            # next unless we are ready for this text
            next if defined $value->{requires}
                and not $dep_found{ $value->{requires} };

            # Add to the sorted list
            push @sorted_arr, $value->{text};

            # Remember that we found it
            $dep_found{ $value->{id} }++;
        }

        if (scalar @_ == $last_count) die "some requirements don't exist or there is a dependency loop";
        $last_count = scalar @_;
    }

    return \@sorted_arr;
}

Это не очень эффективно и, вероятно, работает в O(n log n) время или что-то в этом роде, но если у вас нет огромного набора данных, возможно, это нормально.

Ответ 3

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

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

Ответ 4

  • вы не сказали, что делать с зависимостями "независимы" друг от друга.

    например. id1 требует id2; id3 требует id4; id3 требует id5. Каким должен быть порядок? (кроме 1 до 2 и 3 перед обоими 4/5)

  • В основном вы хотите, чтобы BFS (Breadth First Search) дерева (ориентированный граф) зависимостей (или леса в зависимости от ответов на # 1) - лес, являющийся набором несвязанных деревьев).

    Для этого:

    • Найти все корневые узлы (ids, которые сами не имеют требования)

      Вы можете легко сделать это, сделав хэш ВСЕХ идентификаторов, используя grep в вашей структуре данных

    • Поместите все эти корневые режимы в исходный массив.

    • Затем реализуем BFS. Если вам нужна помощь по внедрению базовой BFS с использованием массива и цикла в Perl, задайте отдельный вопрос. Может быть модуль CPAN, но алгоритм/код довольно тривиальный (по крайней мере, как только вы написали его один раз:)