Perl - 寻找类似关联的集合

时间:2013-10-23 08:17:10

标签: perl

我有以下数据集

 A       C
 A       S
 B       F
 B       Q
 C       A
 C       I
 D       K
 E       Y
 F       B
 F       R
 I       Y
 K       P

第一列中的每个值在第二列中都有一个关联值。第1行中的值“A”具有关联值“C”。在第二行中,值“A”具有关联的值“S”。

使用Perl,我想找到所有相关值的集合。使用上面的规则,我会得到集合(ACEISY),(BFQR)和(DKP)。

我正在寻找有关如何解决此问题的算法或示例的建议。我不确定哈希表是否适合用于此目的的数据结构。任何帮助将不胜感激。

以下是我的实施:

while<INPUT>{
    my ($c1, $c2) = split;
    my %clusterhash = ();
    if (exists $clusterhash{$c1}){
        if (exists $clusterhash{$c1}{$c2}){
            #do nothing
        }
        else {
            $clusterhash{$c1}{$c2} = $c2;
        }
    }
    else{
        foreach my $key ( keys %clusterhash ) {
            if (exists $clusterhash{$key}{$c1}{
                $clusterhash{$c1}{$key} = $key;
            }
        }
        $clusterhash{$c1}{$c2} = $c2;
    }
}

2 个答案:

答案 0 :(得分:5)

您的数据集可以被视为可能已断开连接的有向图。在我看来,你想要为每个弱连接的子图设置节点。自己写这篇文章并不困难:

  • 我们将图表视为无向图。
  • 我们将边缘存储在哈希中,以便条目$edge{$a}{$b}是从顶点$a$b的有向边。
  • 现在我们所需要的只是一次迭代搜索,删除所有访问过的边缘。

示例代码:

use strict; use warnings; use feature qw/say/;

# build the graph
my %edge;
while (<>) {
  my ($from, $to) = split;
  $edge{$from}{$to} = $edge{$to}{$from} = undef;
}

while (my ($start) = keys %edge) {
  my @seen  = ($start);
  my @stack = ($start);

  while (@stack) {
    my $vertex = pop @stack;

    # delete edges from and to this vertex
    # NB: any connections to seen vertices are already removed.
    my @reachable = keys %{ delete($edge{$vertex}) // {} };
    delete $edge{$_}{$vertex} for @reachable;

    # mark new vertices as seen, and enqueue them
    push @seen, @reachable;
    push @stack, @reachable;
  }

  my $nodes = join ', ', sort @seen;
  say "node set: {$nodes}";
}

您的数据输出:

node set: {B, F, Q, R}
node set: {D, K, P}
node set: {A, C, E, I, S, Y}

此算法已经相当优化,并且在 O n · k )时间和空间中运行(其中 k < / em>是邻居的平均数量。)

当然,已经有一个实现图算法的模块。毫无疑问,它被称为Graph。上面的代码相当于:

use strict; use warnings; use feature qw/say/;
use Graph;

my $graph = Graph::Undirected->new;
while (<>) {
  my ($from, $to) = split;
  $graph->add_edge($from, $to);
}

for my $nodes_array ($graph->connected_components) {
  my $nodes = join ', ', sort @$nodes_array;
  say "node set: {$nodes}";
}

通过在构建图形时跟踪连接的组件,可以在更少的内存中执行这些计算,并且可能花费更少的时间。为此,我们有一个哈希,将顶点映射到它们的子图。

  1. 如果边缘的两个顶点都是未知的,则会创建一个新的子图。
  2. 如果只知道一个顶点,则另一个节点映射到第一个节点的子图,并在那里列为成员。
  3. 如果两个顶点都已知,那么
    1. 如果他们指向同一个子图,则没有任何反应。
    2. 如果他们指向不同的子图,则所有条目都会更新为指向同一子图,该子图现在包含先前子图的组合节点。
  4. 代码:

    use strict; use warnings; use feature qw/say/;
    
    my %subgraph_by_id;
    my %subgraph_by_vertex;
    while(<>) {
      my ($x, $y) = split;
      # case 1:
      # If an both vertices of an edge are unknown, they create a new subgraph.
      if (not exists $subgraph_by_vertex{$x} and not exists $subgraph_by_vertex{$y}) {
        my $new = [$x, $y];
        $subgraph_by_id{0+ $new} = $new;
        $subgraph_by_vertex{$_} = $new for $x, $y;
      }
      # case 2:
      # If exactly one vertex is known, the other node maps to the subgraph of the
      # first node, and is listed there as a member.
      elsif (not exists $subgraph_by_vertex{$x} or not exists $subgraph_by_vertex{$y}) {
        my ($known, $unknown) = (exists $subgraph_by_vertex{$x}) ? ($x, $y) : ($y, $x);
        my $subgraph = $subgraph_by_vertex{$unknown} = $subgraph_by_vertex{$known};
        push @$subgraph, $unknown;
      }
      # case 3:
      # both vertices are known. If they point to different subgraphs, all entries
      # are updated to point to the same subgraph which now contains the combined
      # nodes of the previous subgraphs.
      # Except all that copying would make for a horrible worst case.
      # Instead, we just add a reference to the smaller list, flattening it later.
      else {
        my $prev_x = $subgraph_by_vertex{$x};
        my $prev_y = $subgraph_by_vertex{$y};
        # don't test for inequality directly to allow subgraph nesting
        if ($subgraph_by_id{0+ $prev_x} != $subgraph_by_id{0+ $prev_y}) {
          my ($new, $old) = (@$prev_x > @$prev_y) ? ($prev_x, $prev_y) : ($prev_y, $prev_x);
          push @$new, $old;
          # $old not needed on top level any longer – associate it with $new by id
          $subgraph_by_id{0+ $old} = 0+ $new;
        }
      }
    }
    
    # skip symbolic IDs
    for my $nodes_array (grep ref, values %subgraph_by_id) {
      my $nodes = join ', ', flatten($nodes_array);
      say "node set: {$nodes}";
    }
    
    sub flatten {
      return map { ref $_ ? flatten($_) : $_ } @{ shift() };
    }
    

    这仅使用 O n )空间和时间,使用了许多尴尬的技巧。在子图的构建过程中,我没有合并两个连接的子图,而是将其推迟到以后。否则,边缘情况(自下而上建立的平衡树 - 对于每个非叶节点,将复制一半树)可能需要指数时间 - 我没有进行全面分析。 0+“venus”伪操作符对其参数进行了编号,此处用于获取数组引用的ID。

答案 1 :(得分:1)

这不应该成为一个答案,而是一个评论,但它变得太长,不适合评论领域:

速度有问题吗?因为,有这么多的数据,经常循环它可能是一个坏主意吗?因为如果循环重复是没有问题而不是哈希将是一个简单的解决方案:采取第1列中尚未在您的哈希中的第一个元素;使用新的设置号将其添加到哈希作为键;迭代所有行,将所有ist关联值作为键添加到散列中,并使用相同的集合号;如果您在上次迭代中添加了一个新密钥,请再次为这些密钥执行此操作,直到您不添加新密钥为止。获取哈希中尚未存在的下一个元素,并使用下一个设置索引重复; 一旦没有未分配的元素,您就将哈希中的所有元素作为键设置为值。 您可能需要最后格式化它。

编辑:好的,如果速度是一个问题,如何缩放值的数量而不是行数?

有一个外部哈希,其中set indizes为键,内部哈希值为值。那些内部哈希将元素作为键,将“1”作为值。通过线条迭代。在每一行中,检查值是否已经是一个或两个内部哈希值中的键。如果它们处于不同的哈希值,请合并这些哈希值并删除外部哈希值中的一个键。如果一个在一个哈希中而另一个不在哈希中,则将新值添加到第一个哈希中,如果它们在同一个哈希中,则不执行任何操作,如果两个哈希都没有,则为外部哈希创建新密钥,将两个值都添加到相应的内部哈希中。

如果内部哈希可能会变大或者可能有很多集,那么这可能会变得非常缓慢。但是,如果可行值的集合与行数相比较小,则可能非常快。

最佳编辑: 我刚才有了另一个想法。这个最多看三行(假设随机关联更可能是两次)我认为合理快但需要更多内存。 用两个大哈希迭代线条。在每一行中,将cell2添加到存储在key1的hash1中的Array上,并将cell1添加到存储在key2的hash2中的Array中。基本上你把所有的信息都读成了这两个哈希..现在你拿一个hash1的随机密钥并将该密钥和correspoinding Array中的所有元素添加到你想要存储最终集合的任何结构中(我假设为密钥)将set number作为值的第三个哈希值,并从hash1中删除密钥。现在,您还将所有这些元素作为hash2中的键查找,并将这些Arrays中的所有内容添加到集合中,并从hash2中删除键。现在,您将已添加到集合中的所有内容作为hash1的键,并再次将Arrays中的所有内容添加到集合中,依此类推。你必须继续这样做,直到hash1和hash2连续都没有任何东西可以添加到集合中。然后你拿另一个随机密钥开始下一组。删除所有使用的密钥可确保您不会两次获得任何内容,并且不会经常检查同一行。这假设查看哈希中是否存在密钥实际上与我认为的一样快。