我有以下数据集
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;
}
}
答案 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}";
}
通过在构建图形时跟踪连接的组件,可以在更少的内存中执行这些计算,并且可能花费更少的时间。为此,我们有一个哈希,将顶点映射到它们的子图。
代码:
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连续都没有任何东西可以添加到集合中。然后你拿另一个随机密钥开始下一组。删除所有使用的密钥可确保您不会两次获得任何内容,并且不会经常检查同一行。这假设查看哈希中是否存在密钥实际上与我认为的一样快。