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

时间:2010-01-25 15:26:17

标签: 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;
}

6 个答案:

答案 0 :(得分:9)

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

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

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

例如,在将A1从0更改为1之后,有3种方法可以步行进行下一次修复:A3,B1和C1。让我们决定第一次编辑应该修复行。所以我们选择A3。在第二次编辑时,我们将修复列,因此我们有选择:B3或C3(比如C3)。最终修复只提供一个选项(C1),因为我们需要返回原始编辑的列。最终结果是一个新的有效矩阵。

@numbers

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

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

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

这是一个例子。这里可能还有进一步的速度优化,但这种方法在我的Windows机器上30秒左右产生了10个新的600x600矩阵,密度为0.5%。不知道那是否足够快。

    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

答案 1 :(得分:5)

步骤1:首先,我将矩阵初始化为零并计算所需的行和列总数。

步骤2:现在选择一个随机行,按照必须在该行中的1的计数加权(因此,与具有权重5的行相比,更有可能选择具有计数300的行。)

步骤3:对于此行,选择一个随机列,按该列中1的计数加权(除了忽略任何可能已经包含1的单元格 - 以后再用它)。

步骤4:在此单元格中放置一个并减少相应行和列的行数和列数。

步骤5:返回步骤2,直到没有行计数为非零。

问题是这个算法可能无法终止,因为你可能有一行你需要放一个,一列需要一个,但你已经在那个单元中放了一个,所以你进度停止了'。我不确定这种情况发生的可能性有多大,但如果频繁发生我也不会感到惊讶 - 足以让算法无法使用。如果这是一个问题,我可以考虑两种方法来解决它:

a)递归构造上述算法,并允许回溯失败。

b)如果没有其他选项,则允许单元格包含大于1的值并继续运行。然后在最后你有一个正确的行和列数,但有些单元格可能包含大于1的数字。你可以通过找到如下所示的分组来解决这个问题:

2 . . . . 0
. . . . . .
. . . . . .
0 . . . . 1

并将其更改为:

1 . . . . 1
. . . . . .
. . . . . .
1 . . . . 0

如果你有很多零,应该很容易找到这样的分组。我认为b)可能会更快。

我不确定这是最好的方法,但它可能比改组阵列更快。我会跟踪这个问题,看看其他人想出了什么。

答案 2 :(得分:1)

我不是数学家,但我认为如果你需要保持相同的列和行总数,那么矩阵的随机版本将具有相同数量的1和0。

如果我错了,请纠正我,但这意味着制作矩阵的后续版本只需要在行和列周围进行随机播放。

随机改组列不会改变行和列的总数,也不会随机改变行。所以,我会做的,首先是洗牌行,然后洗牌。

那应该很快。

答案 3 :(得分:0)

不确定它是否会有所帮助,但您可以尝试从一个角落开始,对于每个列和行,您应该跟踪总和和实际总和。不要试图找到一个好的矩阵,而是尝试将总量视为金额并将其拆分。对于每个元素,找到较小的行总数 - 实际行总数和列总数 - 实际列总数。现在你有了随机数的上限。 清楚吗?抱歉,我不知道Perl,所以我无法显示任何代码。

答案 4 :(得分:0)

和@Gabriel一样,我不是Perl程序员,所以这可能是你的代码已经做的......

您只发布了一个示例。目前尚不清楚是否需要一个随机矩阵,其每行和每列具有与开始矩阵相同数量的1,或者是一个具有相同行和列但是混洗的矩阵。如果后者足够好,你可以创建一个行(或列,无关紧要)索引并随机置换它。然后,您可以按随机索引指定的顺序读取原始数组。无需修改原始数组或创建副本。

当然,这可能无法满足您的要求方面的不明确。

答案 5 :(得分:0)

感谢FMc的Perl代码。基于此解决方案,我使用Python对其进行了重写(供我自己使用,并在此处共享以更清晰),如下所示:

matrix = numpy.array( 
    [[0, 0, 1], 
     [1, 1, 0], 
     [1, 0, 0]]
)

def shuffle(array):
    i = len(array)
    j = 0
    for _ in (array):
        i -= 1;
        j = random.randrange(0, i+1) #int rand($i + 1);
        #print('arrary:', array)
        #print(f'len(array)={len(array)}, (i, j)=({i}, {j})')
        if i != j: 
            tmp = array[i]
            array[i] = array[j]
            array[j] = tmp
    return array

def other_edits(matrix, cell, step, last_j):
    # We have succeeded if we've already made 3 edits.
    step += 1
    if step > 3: 
        return True

    # Determine the roster of next edits to fix the row or
    # column total upset by our prior edit.
    (i, j) = cell
    fixes = []
    if (step == 1):
        fixes = [[i, x] for x in range(len(matrix[0])) if x != j and not matrix[i][x] ]
        fixes = shuffle(fixes)
    elif (step == 2):
        fixes = [[x, j] for x in range(len(matrix)) if x != i and matrix[x][j]]
        fixes = shuffle(fixes)
    else:
        # On the last edit, the column of the fix must be
        # the same as the column of the initial edit.
        if not matrix[i][last_j]: fixes = [[i, last_j]]

    for f in (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]] = 0 if step == 2 else 1
            return True

    # Failure if we get here.
    return False # return False

def cells_to_move(matrix):
    # Returns a list of non-empty cells.
    i = -1
    cells = []
    for row in matrix:
        i += 1;
        for j in range(len(row)):
            if matrix[i][j]: cells.append([i, j])
    return cells

def edit_matrix(matrix):
    # Takes a matrix and moves all of the non-empty cells somewhere else.
    move_these = cells_to_move(matrix)
    for cell in move_these:
        (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.
        if matrix[i][j] and other_edits(matrix, cell, 0, j):
            matrix[i][j] = 0
    return matrix

def Shuffle_Matrix(matrix, N, M, n_iter):
    for n in range(n_iter):
        print(f'iteration: {n+1}') # Show progress.
        matrix = edit_matrix(matrix)
        #print('matrix:\n', matrix)
    return matrix

print(matrix.shape[0], matrix.shape[1]) 

# Args: N rows, N columns, N iterations.
matrix2 = Shuffle_Matrix(matrix, matrix.shape[0], matrix.shape[1], 1) 

print("The resulting matrix:\n", matrix2)