perl - 在perl中随机化矩阵,保持行和列的总数相同

标签 perl random matrix

我有一个矩阵,我想随机化几千次,同时保持行和列的总数相同:

     1 2 3 
   A 0 0 1 
   B 1 1 0 
   C 1 0 0      

一个有效的随机矩阵的例子是:
     1 2 3
   A 1 0 0
   B 1 1 0
   C 0 0 1

我的实际矩阵要大得多(大约 600x600 项),所以我真的需要一种计算效率高的方法。

我最初的(低效)方法包括使用 Perl Cookbook 对数组进行混洗。 shuffle

我在下面粘贴了我当前的代码。如果在 while 循环中没有找到解决方案,我有额外的代码来开始一个新的无序数字列表。该算法适用于小矩阵,但一旦我开始扩大规模,就需要永远找到符合要求的随机矩阵。

有没有更有效的方法来完成我正在寻找的东西?
非常感谢!
#!/usr/bin/perl -w
use strict;

my %matrix = ( 'A' => {'3'  => 1 },
           'B' => {'1'  => 1,
               '2'  => 1 },
           'C' => {'1'  => 1 }
    );

my @letters = ();
my @numbers = ();

foreach my $letter (keys %matrix){
    foreach my $number (keys %{$matrix{$letter}}){
    push (@letters, $letter);
    push (@numbers, $number);
    }
}

my %random_matrix = ();

&shuffle(\@numbers);
foreach my $letter (@letters){
    while (exists($random_matrix{$letter}{$numbers[0]})){
    &shuffle (\@numbers);
    }
    my $chosen_number = shift (@numbers);
    $random_matrix{$letter}{$chosen_number} = 1;
}

sub shuffle {
    my $array = shift;
    my $i = scalar(@$array);
    my $j;
    foreach my $item (@$array )
    {
        --$i;
        $j = int rand ($i+1);
        next if $i == $j;
        @$array [$i,$j] = @$array[$j,$i];
    }
    return @$array;
}

最佳答案

您当前算法的问题在于您正试图摆脱死胡同 - 特别是当您的 @letters@numbers数组(在 @numbers 的初始洗牌之后)不止一次产生相同的单元格。当矩阵很小时,这种方法有效,因为它不需要太多尝试就可以找到可行的重新洗牌。然而,当列表很大时,它是一个杀手。即使您可以更有效地寻找替代方案——例如,尝试排列而不是随机改组——该方法可能注定要失败。

您可以通过对现有矩阵进行小的修改来解决问题,而不是对整个列表进行混洗。

例如,让我们从您的示例矩阵开始(称为 M1)。随机选择一个单元格进行更改(例如 A1)。此时矩阵处于非法状态。我们的目标是以最少的编辑次数来修复它——特别是 3 次以上的编辑。您通过在矩阵周围“走动”来实现这 3 个额外的编辑,每次修复行或列都会产生另一个需要解决的问题,直到走完整个圆圈(错误...完整矩形)。

例如,将A1从0改为1后,下一次修复有3种行走方式:A3、B1、C1。让我们决定第一次编辑应该修复行。所以我们选择A3。在第二次编辑时,我们将修复该列,因此我们可以选择:B3 或 C3(例如 C3)。最后的修复只提供了一个选择(C1),因为我们需要回到我们原来编辑的那一栏。最终结果是一个新的有效矩阵。

    Orig         Change A1     Change A3     Change C3     Change C1
    M1                                                     M2

    1 2 3        1 2 3         1 2 3         1 2 3         1 2 3
    -----        -----         -----         -----         -----
A | 0 0 1        1 0 1         1 0 0         1 0 0         1 0 0
B | 1 1 0        1 1 0         1 1 0         1 1 0         1 1 0
C | 1 0 0        1 0 0         1 0 0         1 0 1         0 0 1

如果编辑路径导致死胡同,您就回溯。如果所有修复路径都失败,则可以拒绝初始编辑。

这种方法将快速生成新的有效矩阵。它不一定会产生随机结果:M1 和 M2 仍将彼此高度相关,随着矩阵大小的增加,这一点将变得更加明显。

你如何增加随机性?您提到大多数单元格(99% 或更多)都是零。一个想法是这样进行:对于矩阵中的每个 1,将其值设置为 0,然后使用上面概述的 4 编辑方法修复矩阵。实际上,您会将所有这些移动到新的随机位置。

这是一个插图。这里可能还有进一步的速度优化,但这种方法在我的 Windows 机器上在 30 秒左右的时间内生成了 10 个新的 600x600 矩阵,密度为 0.5%。不知道这样够不够快。
use strict;
use warnings;

# Args: N rows, N columns, density, N iterations.
main(@ARGV);

sub main {
    my $n_iter = pop;
    my $matrix = init_matrix(@_);
    print_matrix($matrix);
    for my $n (1 .. $n_iter){
        warn $n, "\n"; # Show progress.
        edit_matrix($matrix);
        print_matrix($matrix);
    }
}

sub init_matrix {
    # Generate initial matrix, given N of rows, N of cols, and density.
    my ($rows, $cols, $density) = @_;
    my @matrix;
    for my $r (1 .. $rows){
        push @matrix, [ map { rand() < $density ? 1 : 0  } 1 .. $cols ];
    }
    return \@matrix;
}

sub print_matrix {
    # Dump out a matrix for checking.
    my $matrix = shift;
    print "\n";
    for my $row (@$matrix){
        my @vals = map { $_ ? 1 : ''} @$row;
        print join("\t", @vals), "\n";
    }
}

sub edit_matrix {
    # Takes a matrix and moves all of the non-empty cells somewhere else.
    my $matrix = shift;
    my $move_these = cells_to_move($matrix);
    for my $cell (@$move_these){
        my ($i, $j) = @$cell;
        # Move the cell, provided that the cell hasn't been moved
        # already and the subsequent edits don't lead to a dead end.
        $matrix->[$i][$j] = 0
            if $matrix->[$i][$j]
            and other_edits($matrix, $cell, 0, $j);
    }
}

sub cells_to_move {
    # Returns a list of non-empty cells.
    my $matrix = shift;
    my $i = -1;
    my @cells = ();
    for my $row (@$matrix){
        $i ++;
        for my $j (0 .. @$row - 1){
            push @cells, [$i, $j] if $matrix->[$i][$j];
        }
    }
    return \@cells;
}

sub other_edits {
    my ($matrix, $cell, $step, $last_j) = @_;

    # We have succeeded if we've already made 3 edits.
    $step ++;
    return 1 if $step > 3;

    # Determine the roster of next edits to fix the row or
    # column total upset by our prior edit.
    my ($i, $j) = @$cell;
    my @fixes;
    if ($step == 1){
        @fixes = 
            map  { [$i, $_] }
            grep { $_ != $j and not $matrix->[$i][$_] }
            0 .. @{$matrix->[0]} - 1
        ;
        shuffle(\@fixes);
    }
    elsif ($step == 2) {
        @fixes = 
            map  { [$_, $j] }
            grep { $_ != $i and $matrix->[$_][$j] }
            0 .. @$matrix - 1
        ;
        shuffle(\@fixes);
    }
    else {
        # On the last edit, the column of the fix must be
        # the same as the column of the initial edit.
        @fixes = ([$i, $last_j]) unless $matrix->[$i][$last_j];
    }

    for my $f (@fixes){
        # If all subsequent fixes succeed, we are golden: make
        # the current fix and return true.
        if ( other_edits($matrix, [@$f], $step, $last_j) ){
            $matrix->[$f->[0]][$f->[1]] = $step == 2 ? 0 : 1;
            return 1;
        }
    }

    # Failure if we get here.
    return;
}

sub shuffle {
    my $array = shift;
    my $i = scalar(@$array);
    my $j;
    for (@$array ){
        $i --;
        $j = int rand($i + 1);
        @$array[$i, $j] = @$array[$j, $i] unless $i == $j;
    }
}

关于perl - 在perl中随机化矩阵,保持行和列的总数相同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2133268/

相关文章:

python - Numpy 无法在内存中存储大于 1GB 的矩阵

python - 在脚本中从 FileMaker Pro 数据库中提取数据的最佳方式?

Python:最后的While循环不断生成新的随机卡,如何让它生成一次并停止?

php - 如何在 PHP 中随机填充单个锦标赛淘汰而不重复?

arrays - 在 MATLAB 中比较两个相同大小的向量

java - 如何确定矩阵中相同邻居的数量?

regex - 在 Perl 中,如何加速正则表达式来修改一个非常大的字符串?

regex - 在 find 命令中转义哪些字符

perl - 你如何只从 `for` 导入 `Perl6::Controls` ?

php - mt_rand() 总是给我相同的数字