【问题标题】:How to calculate the tree the results by combining individual leaf paths?如何通过组合单个叶子路径来计算树的结果?
【发布时间】:2013-11-29 22:17:45
【问题描述】:

假设我有一个输入文件,其中每一行都包含从根 (A) 到叶子的路径

echo "A\tB\tC\nA\tB\tD\nA\tE" > lines.txt
A   B   C
A   B   D
A   E

如何轻松生成结果树?:(A(B(C,D),E))

我想使用 GNU 工具(awk、sed 等),因为它们往往更适合处理大文件,但 R 脚本也可以。 R 输入将是:

# lines <- lapply(readLines("lines.txt"), strsplit, " +")
lines <- list(list(c("A", "B", "C")), list(c("A", "B", "D")), list(c("A","E")))

【问题讨论】:

  • R is 是一个“GNU 工具”:) 如果您想使用 awk 或 sed,输出会是什么?在我看来,无论如何您都希望将输出作为 R 中的列表,不是吗?
  • 对列表进行排序,然后取连续两行中最大的前缀。第二行的后缀是新的。
  • 我会使用 perl 哈希。您使用路径部分沿着散列树向下走。完成后,将元素的子键括在括号中,等等。 Python 也会有类似的概念。
  • @janos 一个字符串可以工作
  • @Coroos 我不确定我是否理解你的方法,你能详细说明一下吗?

标签: r algorithm shell data-structures tree


【解决方案1】:

在 Perl 中:

#!/usr/bin/env perl

use strict;

my $t = {};
while (<>) {
    my @a = split;
    my $t1 = $t;
    while (my $a = shift @a) {
        $t1->{$a} = {} if not exists $t1->{$a};
        $t1 = $t1->{$a};
    }
}

print &p($t)."\n";

sub p {
    my ($t) = @_;
    return
    unless keys %$t;

    return '('
        . join(',', map { $_ . p($t->{$_}) } sort keys %$t)
        . ')';
}

此脚本返回:

% cat <<EOF | perl l.pl
A   B   C
A   B   D
A   E
EOF
(A(B(C,D),E))

请注意,由于 p 中的递归,此脚本根本不适合大型数据集。但这可以通过将其转换为双 for 循环来轻松解决,就像上面的第一个 while 一样。

【讨论】:

    【解决方案2】:

    如果您可以改用 Bourne Shell 脚本,为什么要使用简单的方法呢?请注意,这甚至不是 Bash,这是普通的旧 Bourne shell,没有数组...

    #!/bin/sh
    #
    # A B C
    # A B D
    # A E
    #
    # "" vs "A B C"         -> 0->3, ident 0        -> -0+3 -> "(A(B(C"
    # "A B C" vs "A B D"    -> 3->3, ident 2        -> -1+1 -> ",D"
    # "A B D" vs "A E"      -> 3->2, ident 1        -> -2+1 -> "),E"
    # "A E" vs. endc        -> 2->0, ident 0        -> -2+0 -> "))"
    #
    # Result: (A(B(C,D),E))
    #
    # Input stream is a path per line, path segments separated with spaces.
    
    process_line () {
        local line2="$@"
        n2=$#
        set -- $line1
        n1=$#
    
        s=
        if [ $n2 = 0 ]; then                # last line (empty)
            for s1 in $line1; do
                s="$s)"
            done
        else
            sep=
            remainder=false
    
            for s2 in $line2; do
                if ! $remainder; then
                    if [ "$1" != $s2 ]; then
                        remainder=true
                        if [ $# = 0 ]; then # only children
                            sep='('
                        else                # sibling to an existing element
                            sep=,
                            shift
                            for s1 in $@; do
                                s="$s)"
                            done
                        fi
                    fi
                fi
    
                if $remainder; then         # Process remainder as mismatch
                    s="$s$sep$s2"
                    sep='('
                fi
    
                shift                       # remove the first element of line1
            done
        fi
    
        result="$result$s"
    }
    
    result=
    line1=
    (
        cat - \
        | sed -e 's/[[:space:]]\+/ /' \
        | sed -e '/^$/d' \
        | sort -u
        echo ''                             # last line marker
    ) | while read line2; do
            process_line $line2
            line1="$line2"
    
    
            test -n "$line2" \
                || echo $result
        done
    

    这会为两个不同的文件生成正确答案(l.sh 是 shell 版本,l.pl 是 Perl 版本):

    % for i in l l1; do cat $i; ./l.sh < $i; ./l.pl < $i; echo; done
    A
    A B
    A B C D
    A B E F
    A G H
    A G H I
    (A(B(C(D),E(F)),G(H(I))))
    (A(B(C(D),E(F)),G(H(I))))
    
    A B C
    A B D
    A E
    (A(B(C,D),E))
    (A(B(C,D),E))
    

    呵呵!

    【讨论】:

      【解决方案3】:

      好的,我想我明白了:

      # input
      lines <- c(list(c("A", "B", "C")), list(c("A", "B", "D")), list(c("A","E")))
      
      # generate children
      generate_children <- function(lines){
          children <- list()
          for (line in lines) {
              for (index in 1:(length(line)-1)){
                  parent <- line[index]
                  next_child <- line[index + 1]
                  if (is.null(children[[parent]])){
                      children[[parent]] <- next_child
                  } else {
                      if (next_child %notin% children[[parent]]){
                          children[[parent]] <- c(children[[parent]], next_child)
                      }
                  }
              }
          }
          children
      }
      
      expand_children <- function(current_parent, children){
          if (current_parent %in% names(children)){
              expanded_children <- sapply(children[[current_parent]], function(current_child){
                  expand_children(current_child, children)
              }, USE.NAMES = FALSE)
              output <- setNames(list(expanded_children), current_parent)
          } else {
              output <- current_parent
          }
          output
      }
      
      children <- generate_children(lines)
      root <- names(children)[1]
      tree <- expand_children(root, children)
      dput(tree)
      # structure(list(A = structure(list(B = c("C", "D"), "E"), .Names = c("B",""))), .Names = "A")
      

      有没有更简单的答案?

      【讨论】:

        猜你喜欢
        • 2013-01-03
        • 2022-01-26
        • 1970-01-01
        • 2012-03-27
        • 2013-02-26
        • 2021-02-13
        • 1970-01-01
        • 2018-02-28
        • 1970-01-01
        相关资源
        最近更新 更多