【问题标题】:Graph traversal using awk使用 awk 进行图遍历
【发布时间】:2014-08-23 10:52:52
【问题描述】:

考虑到以下代表循环图的文件,我正在寻找一个 shell 脚本来查找从图中的任何节点开始的所有可到达节点?

A.txt(每行的第一个元素是节点,其余是从它可以到达的相邻节点):

a3 a4
a2 a4 a5
a4 a5
a5 a6
a6 a7
a7 a8
a8 a9
a9 

所需的输出文件(B.txt 和 C.txt)必须是 DFS 或 BFS 的输出,并且包括从任何开始节点到其可到达节点的深度(距离)。比如:

B.txt

a3 a4 a5 a6 a7 a8 a9
a2 a4 a5 a6 a7 a8 a9
a4 a5 a6 a7 a8 a9
a5 a6 a7 a8 a9
a6 a7 a8 a9
a7 a8 a9
a8 a9
a9 

C.txt

0 1 2 3 4 5 6
0 1 1 2 3 4 5
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0 

Awk 是首选,尽管任何类型的 shell 脚本都可以。

【问题讨论】:

  • 这里有些不清楚。根据您的可达性输出,很明显该图是有向的:a8 到达 a9,但 a9 没有到达 a8。其次,由于某种原因,每个节点都会到达自己。第三,这是示例图是“循环”的唯一意义。在其他方面它是非循环的:每个顶点 An 只有与 m > n 相邻的更高编号的顶点 Am。没有反向引用(除了隐式从 An 到 An)。
  • Perl 解决方案假定图是非循环的,并且对于任何 m > n,节点 Am 可以从 An 到达,而其他节点不可到达。因此,节点可以排列成一个序列,我们只需要提取该序列的后缀。这不是从图中的任意点(即使在 DAG 中)开始的适当的深度优先搜索。考虑像 (a1 a2) (a1 a3) (a3 a4) (a3 a5) (a2 a6) (a2 a7) 这样的图:平衡二叉树。从节点 a3 开始,我们只到达 a4 和 a5。从 a2,我们只到达 a6 和 a7:节点的不同子集,它们不是公共序列的后缀。

标签: shell awk graph-theory text-processing


【解决方案1】:

改编自https://stackoverflow.com/a/25085230/1745001,这是一个awk解决方案:

$ cat tst.awk
function calcMinDepth(parent,  childArray, childNr, child) {
    ++depth
    split(children[parent],childArray)
    for (childNr=1; childNr in childArray; childNr++) {
        child = childArray[childNr]
        if ( (minDepth[origin,child] > depth) ||
                (minDepth[origin,child] == 0) ) {
            minDepth[origin,child] = depth
            calcMinDepth(child)
        }
    }
    --depth
    return
}

function prtInfo(parent,  childArray, childNr, child) {
    split(children[parent],childArray)
    for (childNr=1; childNr in childArray; childNr++) {
        child = childArray[childNr]
        if (!seen[child]++) {
            printf " %s", child > "B.txt"
            printf " %d", minDepth[origin,child] > "C.txt"
            prtInfo(child)
        }
    }
    return
}

{
    parents[++numParents] = $1
    for (i=2; i<=NF; i++) {
        children[$1] = (i>2 ? children[$1] FS : "") $i
    }
}

END {
    for (parentNr=1; parentNr<=numParents; parentNr++) {
        origin = parents[parentNr]
        calcMinDepth(origin)
    }

    for (parentNr=1; parentNr<=numParents; parentNr++) {
        origin = parents[parentNr]
        printf "%s", origin > "B.txt"
        printf "%d", depth  > "C.txt"
        prtInfo(origin)
        print "" > "B.txt"
        print "" > "C.txt"
        delete seen     # or split("",seen) for non-gawk
    }
}

.

$ awk -f tst.awk file

$ cat B.txt
a3 a4 a5 a6 a7 a8 a9
a2 a4 a5 a6 a7 a8 a9
a4 a5 a6 a7 a8 a9
a5 a6 a7 a8 a9
a6 a7 a8 a9
a7 a8 a9
a8 a9
a9

$ cat C.txt
0 1 2 3 4 5 6
0 1 1 2 3 4 5
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0

【讨论】:

    【解决方案2】:

    有趣的问题。如果您可以使用perl,那么这是一种方法:

    perl -lane '
        $seen{$F[0]}++ or push @seq, $F[0];
        push @rec, [ split ]; 
    }{ 
       @seq = sort @seq;
       for my $ref (@rec) {
           my ($idx) = grep { $seq[$_] eq $ref->[-1] } 0..$#seq;
           print join " ", @$ref, @seq[$idx+1..$#seq] ;
       }
    ' file
    a3 a4 a5 a6 a7 a8 a9
    a2 a4 a5 a6 a7 a8 a9
    a4 a5 a6 a7 a8 a9
    a5 a6 a7 a8 a9
    a6 a7 a8 a9
    a7 a8 a9
    a8 a9
    a9
    

    我们在这里所做的是在空白处分割行。我们创建了一个哈希%seen,它查找重复值并过滤它们以防止将它们推送到一个名为@seq 的数组中。这里的想法是建立一个唯一的序列列表。

    我们拆分每一行并将其推送到一个名为@rec 的数组中。在}{ 表示的END 块中,我们对序列进行排序。对于每个数组元素,我们首先在序列数组中找到行的最后一个元素的索引。想法是打印随后的所有内容。一旦我们有了索引,我们就会打印你的输出。

    您可以将输出重定向到另一个文件。

    我不确定你最后的输出。如果您可以添加有关它的更多详细信息,那么我可以尝试提出一些建议。

    【讨论】:

    • 太棒了。太感谢了。我自己会尝试编写其等效的 awk 版本。
    • 当然。但是,因为我刚开始写脚本,所以写它需要一些时间。
    猜你喜欢
    • 2017-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-28
    相关资源
    最近更新 更多