【问题标题】:Capturing Non-Zero Elements, Counts and Indexes of Sparse Matrix捕获稀疏矩阵的非零元素、计数和索引
【发布时间】:2010-11-22 12:48:13
【问题描述】:

我有以下稀疏矩阵A。

   2   3   0   0   0
   3   0   4   0   6
   0  -1  -3   2   0
   0   0   1   0   0
   0   4   2   0   1

然后我想从那里捕获以下信息:

  1. 条目的累积计数,因为矩阵是按列扫描的。 产量:

    Ap = [ 0, 2, 5, 9, 10, 12 ];

  2. 条目的行索引,因为矩阵是按列扫描的。 产量:

    Ai = [0, 1, 0, 2, 4, 1, 2, 3, 4, 2, 1, 4 ];

  3. 非零矩阵条目,因为矩阵是按列扫描的。 产量:

    Ax = [2, 3, 3, -1, 4, 4, -3, 1, 2, 2, 6, 1];

由于实际的矩阵 A 可能非常大,有没有什么有效的方法 在 Perl 中可以捕获那些元素?尤其是在不破坏所有矩阵 A 的情况下 进入内存。

我被以下代码卡住了。这不能满足我的需求。

use strict;
use warnings;

my (@Ax, @Ai, @Ap) = ();
while (<>) {
    chomp;
    my @elements = split /\s+/;
    my $i = 0;
    my $new_line = 1;
    while (defined(my $element = shift @elements)) {
        $i++;
        if ($element) {
            push @Ax, 0 + $element;
            if ($new_line) {
                push @Ai, scalar @Ax;
                $new_line = 0;
            }

            push @Ap, $i;
        }
    }
}
push @Ai, 1 + @Ax;
print('@Ax  = [', join(" ", @Ax), "]\n");
print('@Ai = [', join(" ", @Ai), "]\n");
print('@Ap = [', join(" ", @Ap), "]\n");

【问题讨论】:

  • @foolishbrat 顺便说一下,将 print "\@Ax = [@Ax]\n"; 与您所拥有的进行比较。

标签: perl algorithm sparse-matrix memory-efficient


【解决方案1】:

更新基于 FM 的评论。如果您不想存储任何原始数据:

#!/usr/bin/perl

use strict;
use warnings;

my %matrix_info;

while ( <DATA> ) {
    chomp;
    last unless /[0-9]/;
    my @v = map {0 + $_ } split;
    for (my $i = 0; $i < @v; ++$i) {
        if ( $v[$i] ) {
            push @{ $matrix_info{$i}->{indices} }, $. - 1;
            push @{ $matrix_info{$i}->{nonzero} }, $v[$i];
        }
    }
}

my @cum_count = (0);
my @row_indices;
my @nonzero;

for my $i ( sort {$a <=> $b } keys %matrix_info ) {
    my $mi = $matrix_info{$i};
    push @nonzero, @{ $mi->{nonzero} };

    my @i = @{ $mi->{indices} };

    push @cum_count, $cum_count[-1] + @i;
    push @row_indices, @i;
}

print(
    "\@Ap = [@cum_count]\n",
    "\@Ai = [@row_indices]\n",
    "\@Ax = [@nonzero]\n",
);

__DATA__
2   3   0   0   0
3   0   4   0   6
0  -1  -3   2   0
0   0   1   0   0
0   4   2   0   1

输出:

C:\温度> 米 @Ap = [0 2 5 9 10 12] @Ai = [0 1 0 2 4 1 2 3 4 2 1 4] @Ax = [2 3 3 -1 4 4 -3 1 2 2 6 1]

【讨论】:

  • 也许我误解了一些东西,但你的第一个答案给人的印象是@cum_count 就像 OP 的累积计数 (@Ap),但事实并非如此。您的计数是按行计数的;他想要按列。也就是说,OP 需要的计数在%row_indices(例如scalar @{$row_indices{0}})中提供,所以答案仍然是一个好答案。
【解决方案2】:

存储稀疏数据的常用策略是删除您不关心的值(零),并将行和列索引与您关心的每个值一起存储,从而保留它们的位置信息:

[VALUE, ROW, COLUMN]

在您的情况下,您可以进一步节省成本,因为可以通过逐列处理数据来满足您的所有需求,这意味着我们不必为每个值重复 COLUMN。

use strict;
use warnings;
use Data::Dumper;

my ($r, $c, @dataC, @Ap, @Ai, @Ax, $cumul);

# Read data row by row, storing non-zero values by column.
#    $dataC[COLUMN] = [
#        [VALUE, ROW],
#        [VALUE, ROW],
#        etc.
#    ]
$r = -1;
while (<DATA>) {
    chomp;
    $r ++;
    $c = -1;
    for my $v ( split '\s+', $_ ){
        $c ++;
        push @{$dataC[$c]}, [$v, $r] if $v;
    }
}

# Iterate through the data column by column
# to compute the three result arrays.
$cumul = 0;
@Ap = ($cumul);
$c = -1;
for my $column (@dataC){
    $c ++;
    $cumul += @$column;
    push @Ap, $cumul;
    for my $value (@$column){
        push @Ax, $value->[0];
        push @Ai, $value->[1];
    }
}

__DATA__
2   3   0   0   0
3   0   4   0   6
0  -1  -3   2   0
0   0   1   0   0
0   4   2   0   1

【讨论】:

    【解决方案3】:

    我猜这就是你要找的东西:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Data::Dumper::Simple;
    
    my @matrix;
    
    # Populate @matrix
    while (<>) {
        push @matrix, [ split /\s+/ ];
    }
    
    my $columns = @{ $matrix[0] };
    my $rows    = @matrix;
    
    my ( @Ap, @Ai, @Ax );
    my $ap = 0;
    
    for ( my $j = 0 ; $j <= $rows ; $j++ ) {
        for ( my $i = 0 ; $i <= $columns ; $i++ ) {
            if ( $matrix[$i]->[$j] ) {
                $ap++;
                push @Ai, $i;
                push @Ax, $matrix[$i]->[$j];
            }
        }
        push @Ap, $ap;
    }
    
    print Dumper @Ap;
    print Dumper @Ai;
    print Dumper @Ax;
    

    【讨论】:

    • @AHA:谢谢。但是您的方法仍然将整个矩阵吞入@matrix。在实践中矩阵维度是 10^6。我认为我的 RAM 买不起。
    • @foolishbrat:哦,我明白了。 @Inshalla:对不起,我不明白。
    【解决方案4】:

    您提供的代码是逐行运行的。要按列获得连续的结果,您必须将值累积到单独的数组中,每列一个:

    # will look like ([], [], [] ...), one [] for each column.
    my @columns;
    
    while (<MATRIX>) {
        my @row = split qr'\s+';
        for (my $col = 0; $col < @row; $col++) {
    
            # push each non-zero value into its column
            push @{$columns[$col]}, $row[$col] if $row[$col] > 0;
    
        }
    }
    
    # now you only need to flatten it to get the desired kind of output:
    use List::Flatten;
    @non_zero = flat @columns;
    

    另见List::Flatten

    【讨论】:

      【解决方案5】:

      Ap 很简单:只需从零开始,每次遇到非零数时递增。我没有看到你试图在 @Ap 中写任何东西,所以它没有如你所愿地结束也就不足为奇了。

      Ai 和 Ax 比较棘手:您希望在按行扫描时按列排序。您将无法就地执行任何操作,因为您还不知道列将产生多少元素,因此您无法提前知道元素的位置。

      显然,如果您可以将要求更改为按行排序,那会容易得多。否则,您可能会变得复杂并收集 (i, j, x) 三元组。在收集时,它们自然会按 (i, j) 排序。收集后,您只想按 (j, i) 对它们进行排序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-28
        • 1970-01-01
        • 1970-01-01
        • 2011-11-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多