我有一个由节点和它们的后继边表示的DAG。使用一个简单的递归函数可以将其作为嵌套数据结构重建。
#tree1.pl
#!/usr/bin/env perl
use 5.028; use strictures; use Moops; use Kavorka qw(fun); use List::AllUtils qw(first);
class Node :ro {
has label => isa => Str;
has children => isa => ArrayRef[Str];
}
fun N($label, $children) {
return Node->new(label => $label, children => $children);
}
# list is really flat, but
# indentation outlines desired tree structure
our @dag = (
N(N0 => ['N1']),
N(N1 => ['N2']),
N(N2 => ['N3']),
N(N3 => ['N4', 'N5']),
N(N4 => []),
N(N5 => []),
);
fun tree(Node $n) {
return bless [
map {
my $c = $_;
tree(first {
$_->label eq $c
} @dag)
} $n->children->@*
] => $n->label;
}
tree($dag[0]);
# bless([ #N0
# bless([ #N1
# bless([ #N2
# bless([ #N3
# bless([] => 'N4'),
# bless([] => 'N5'),
# ] => 'N3')
# ] => 'N2')
# ] => 'N1')
# ] => 'N0')
那只是一个小问题。
在我的应用程序中,我遇到了一个复杂的问题,即DAG包含多个具有相同标签的节点。
our @dag = (
N(N0 => ['N1']),
N(N1 => ['N2']),
︙
N(N1 => ['N6', 'N5']),
︙
请注意,这并不意味着在适当的意义上存在多重边。
这是错误的,因为现在N1似乎有三个相等的子节点。
为了图形遍历的目的,N1节点不能折叠成一个节点,只能用于标记输出树;因此换句话说,这些节点必须具有不同的身份。让我们用颜色来可视化这一点。
our @dag = (
N(N0 => ['N1']),
N([N1 => 'red'] => ['N2']),
︙
N([N1 => 'blue'] => ['N6', 'N5']),
︙
目标是将此DAG实现为两棵树。分别在两个步骤中遵循每个虚线继承边。我通过在遍历节点时记住节点上一个颜色的索引号,并在下一次树构造中按顺序选择下一个颜色来实现这一点。
#tree2.pl
#!/usr/bin/env perl
use 5.028; use strictures; use Moops; use Kavorka qw(fun); use List::AllUtils qw(first);
class Node :ro {
has label => isa => Str;
has col => isa => Maybe[Str];
has children => isa => ArrayRef[Str];
has col_seen => is => 'rw', isa => Int;
}
fun N($c_l, $children) {
return ref $c_l
? Node->new(label => $c_l->[0], col => $c_l->[1], children => $children)
: Node->new(label => $c_l, children => $children);
}
# indentation outlines desired tree structure
our @dag = (
### start 1st tree
N(N0 => ['N1']),
N([N1 => 'red'] => ['N2']),
N(N2 => ['N3']),
N(N3 => ['N4', 'N5']),
N(N4 => []),
N(N5 => []),
### end 1st tree
### start 2nd tree
# N0
N([N1 => 'blue'] => ['N6', 'N5']),
N(N6 => ['N7']),
N(N7 => ['N4']),
# N4
# N5
### end 2nd tree
);
fun tree(Node $n) {
return bless [
map {
my $c = $_;
my @col = map { $_->col } grep { $_->label eq $c } @dag;
if (@col > 1) {
$n->col_seen($n->col_seen + 1);
die 'exhausted' if $n->col_seen > @col;
tree(first {
$_->label eq $c && $_->col eq $col[$n->col_seen - 1]
} @dag);
} else {
tree(first { $_->label eq $c } @dag);
}
} $n->children->@*
] => $n->label;
}
tree($dag[0]);
# bless([ #N0
# bless([ #N1
# bless([ #N2
# bless([ #N3
# bless([] => 'N4'),
# bless([] => 'N5')
# ] => 'N3')
# ] => 'N2')
# ] => 'N1')
# ] => 'N0')
tree($dag[0]);
# bless([ #N0
# bless([ #N1
# bless([ #N6
# bless([ #N7
# bless([] => 'N4')
# ] => 'N7')
# ] => 'N6'),
# bless([] => 'N5')
# ] => 'N1')
# ] => 'N0')
tree($dag[0]);
# exhausted
那段代码有效,我得到了两棵树。
然而,当我有几个带有彩色后继节点的节点时,我的代码存在问题。代码与上面相同,只是输入不同:
#tree3.pl
︙
our @dag = (
N(N0 => ['N1']),
N([N1 => 'red'] => ['N2']),
N(N2 => ['N3']),
N(N3 => ['N4', 'N5']),
N(N4 => []),
N(N5 => []),
# N0
N([N1 => 'blue'] => ['N6', 'N5']),
N(N6 => ['N7']),
N(N7 => ['N8', 'N4']),
N([N8 => 'purple'] => ['N5']),
# N5
N([N8 => 'orange'] => []),
N([N8 => 'cyan'] => ['N5', 'N5']),
# N5
# N5
# N4
# N5
);
︙
tree($dag[0]);
# bless([ #N0
# bless([ #N1
# bless([ #N2
# bless([ #N3
# bless([] => 'N4'),
# bless([] => 'N5')
# ] => 'N3')
# ] => 'N2')
# ] => 'N1')
# ] => 'N0')
tree($dag[0]);
# bless([ #N0
# bless([ #N1
# bless([ #N6
# bless([ #N7
# bless([ #N8
# bless([] => 'N5')
# ] => 'N8'),
# bless([] => 'N4')
# ] => 'N7')
# ] => 'N6'),
# bless([] => 'N5')
# ] => 'N1')
# ] => 'N0')
tree($dag[0]);
# exhausted
问题在于搜索仅在两棵树之后就停止了,尽管我应该得到四棵树:
- 通过红色路径
- 通过蓝色,然后紫色的路径
- 通过蓝色,然后橙色的路径
- 通过蓝色,然后青色的路径
N([id => label] => child)
或N(id => child)
,但不要同时支持两者。显然,第二张图不支持标签,但在图中添加标签只是解决问题的一种方法。2)将tree
编写为不改变$dag
的纯函数,这样将更容易使用,我保证,... - Mulan