Il problema con l'algoritmo attuale è che si sta cercando di mischiare il tuo modo di vicoli ciechi - in particolare, quando i vostri @letters
e @numbers
array (dopo il riordino iniziale di @numbers
) producono la stessa cella più di una volta. Questo approccio funziona quando la matrice è piccola, perché non ci vogliono troppi tentativi per trovare un re-shuffle fattibile. Tuttavia, è un killer quando le liste sono grandi. Anche se potresti cercare le alternative in modo più efficiente, ad esempio provando permutazioni piuttosto che mischiare casualmente, l'approccio è probabilmente destinato a fallire.
Invece di mischiare interi elenchi, è possibile affrontare il problema apportando piccole modifiche a una matrice esistente.
Ad esempio, iniziamo con la matrice di esempio (chiamiamola M1). Seleziona casualmente una cella per cambiare (ad esempio, A1). A questo punto la matrice si trova in uno stato illegale. Il nostro obiettivo sarà quello di risolverlo nel numero minimo di modifiche, in particolare 3 ulteriori modifiche. Implementa queste 3 modifiche aggiuntive "camminando" intorno alla matrice, con ogni riparazione di una riga o colonna che produce un altro problema da risolvere, fino a quando non hai percorso il cerchio completo (err ... rettangolo completo).
Ad esempio, dopo aver modificato A1 da 0 a 1, ci sono 3 modi di camminare per la riparazione successiva: A3, B1 e C1. Decidiamo che la prima modifica debba correggere le righe. Quindi selezioniamo A3. Nella seconda modifica, correggeremo la colonna, quindi abbiamo le scelte: B3 o C3 (per esempio, C3). La riparazione finale offre solo una scelta (C1), perché dobbiamo tornare alla colonna della nostra modifica originale.Il risultato finale è una nuova matrice valida.
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
Se un percorso di modifica porta ad un vicolo cieco, si marcia indietro. Se tutti i percorsi di riparazione falliscono, la modifica iniziale può essere rifiutata.
Questo approccio genererà rapidamente nuove matrici valide. Non produrrà necessariamente risultati casuali: M1 e M2 saranno ancora altamente correlati l'uno con l'altro, un punto che diventerà più direttamente evidente man mano che la dimensione della matrice cresce.
Come si aumenta la casualità? Hai detto che la maggior parte delle celle (99% o più) sono zero. Un'idea sarebbe quella di procedere in questo modo: per ogni 1 nella matrice, impostare il suo valore su 0 e quindi riparare la matrice usando il metodo di modifica a 4 delineato sopra. In effetti, dovresti spostare tutti quelli in posizioni nuove e casuali.
Ecco un'illustrazione. Ci sono probabilmente ulteriori ottimizzazioni della velocità qui, ma questo approccio ha prodotto 10 nuove matrici 600x600, con una densità dello 0,5%, in 30 secondi circa sulla mia scatola di Windows. Non so se è abbastanza veloce.
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;
}
}
È sempre una matrice binaria (gli elementi di matrice sono 0 o 1)? – mob
Sì, la matrice è sempre binaria. Inoltre, anche le istanze di 0 superano di gran lunga le istanze di 1! – Lucas
È sempre quadrato? –