Le problème que je rencontre est d'essayer de trouver un moyen efficace de trouver des éléments permutables dans une matrice afin de mettre en œuvre un algorithme de permutation pour la création de modèles nuls.
La matrice est constituée de 0 et de 1 et l'idée est que les éléments peuvent être échangés entre les colonnes de sorte que les totaux des lignes et des colonnes de la matrice restent les mêmes.
Par exemple, étant donné la matrice suivante :
c1 c2 c3 c4
r1 0 1 0 0 = 1
r2 1 0 0 1 = 2
r3 0 0 0 0 = 0
r4 1 1 1 1 = 4
------------
2 2 1 2
les colonnes c2 et c4 de r1 et r2 peuvent chacune être permutées de manière à ce que les totaux ne soient pas modifiés, c'est-à-dire :
c1 c2 c3 c4
r1 0 0 0 1 = 1
r2 1 1 0 0 = 2
r3 0 0 0 0 = 0
r4 1 1 1 1 = 4
------------
2 2 1 2
Tout cela doit être fait de manière aléatoire afin de ne pas introduire de biais.
J'ai une solution qui fonctionne. Je choisis au hasard une ligne et deux colonnes. Si elles produisent un motif 10 ou 01, je sélectionne au hasard une autre ligne et je vérifie les mêmes colonnes pour voir si elles produisent le motif opposé. Si l'un ou l'autre échoue, je recommence et je sélectionne un nouvel élément.
Cette méthode fonctionne mais je ne "trouve" les bons modèles que dans 10 % des cas. Dans une grande matrice ou dans une matrice avec peu de 1 dans les rangées, je perds beaucoup de temps à "manquer". Je me suis dit qu'il devait y avoir une manière plus intelligente de choisir les éléments de la matrice tout en continuant à le faire de manière aléatoire.
Le code de la méthode de travail est le suivant :
def isSwappable(matrix: Matrix): Tuple2[Tuple2[Int, Int], Tuple2[Int, Int]] = {
val indices = getRowAndColIndices(matrix)
(matrix(indices._1._1)(indices._2._1), matrix(indices._1._1)(indices._2._2)) match {
case (1, 0) => {
if (matrix(indices._1._2)(indices._2._1) == 0 & matrix(indices._1._2)(indices._2._2) == 1) {
indices
}
else {
isSwappable(matrix)
}
}
case (0, 1) => {
if (matrix(indices._1._2)(indices._2._1) == 1 & matrix(indices._1._2)(indices._2._2) == 0) {
indices
}
else {
isSwappable(matrix)
}
}
case _ => {
isSwappable(matrix)
}
}
}
def getRowAndColIndices(matrix: Matrix): Tuple2[Tuple2[Int, Int], Tuple2[Int, Int]] = {
(getNextIndex(rnd.nextInt(matrix.size), matrix.size), getNextIndex(rnd.nextInt(matrix(0).size), matrix(0).size))
}
def getNextIndex(i: Int, constraint: Int): Tuple2[Int, Int] = {
val newIndex = rnd.nextInt(constraint)
newIndex match {
case `i` => getNextIndex(i, constraint)
case _ => (i, newIndex)
}
}
J'ai pensé qu'une manière plus efficace de gérer cela était de supprimer toutes les lignes qui ne pouvaient pas être utilisées (tous les 1 ou 0) et de choisir ensuite un élément au hasard. À partir de là, je pourrais filtrer toutes les colonnes de la ligne qui ont la même valeur et choisir parmi les colonnes restantes.
Une fois que la première ligne et la première colonne sont choisies, je filtre les lignes qui ne peuvent pas fournir le modèle requis et je choisis ensuite parmi les lignes restantes.
Cela fonctionne en grande partie, mais le problème que je ne parviens pas à résoudre est le suivant : que se passe-t-il lorsqu'il n'y a pas de colonnes ou de lignes à choisir ? Je ne veux pas tourner en boucle à l'infini pour essayer de trouver le motif dont j'ai besoin et j'ai besoin d'un moyen de recommencer si j'obtiens une liste vide de lignes ou de colonnes à choisir.
Le code que j'ai jusqu'à présent et qui fonctionne à peu près (jusqu'à ce que j'obtienne une liste vide) est le suivant :
def getInformativeRowIndices(matrix: Matrix) = (
matrix
.zipWithIndex
.filter(_._1.distinct.size > 1)
.map(_._2)
.toList
)
def getRowsWithOppositeValueInColumn(col: Int, value: Int, matrix: Matrix) = (
matrix
.zipWithIndex
.filter(_._1(col) != value)
.map(_._2)
.toList
)
def getColsWithOppositeValueInSameRow(row: Int, value: Int, matrix: Matrix) = (
matrix(row)
.zipWithIndex
.filter(_._1 != value)
.map(_._2)
.toList
)
def process(matrix: Matrix): Tuple2[Tuple2[Int, Int], Tuple2[Int, Int]] = {
val row1Indices = getInformativeRowIndices(matrix)
if (row1Indices.isEmpty) sys.error("No informative rows")
val row1 = row1Indices(rnd.nextInt(row1Indices.size))
val col1 = rnd.nextInt(matrix(0).size)
val colIndices = getColsWithOppositeValueInSameRow(row1, matrix(row1)(col1), matrix)
if (colIndices.isEmpty) process(matrix)
val col2 = colIndices(rnd.nextInt(colIndices.size))
val row2Indices = getRowsWithOppositeValueInColumn(col1, matrix(row1)(col1), matrix)
.intersect(getRowsWithOppositeValueInColumn(col2, matrix(row1)(col2), matrix))
println(row2Indices)
if (row2Indices.isEmpty) process(matrix)
val row2 = row2Indices(rnd.nextInt(row2Indices.size))
((row1, row2), (col1, col2))
}
Je pense que les méthodes récursives sont mauvaises et ne fonctionnent pas vraiment ici. De plus, j'essaie simplement d'améliorer la vitesse de sélection des cellules. Toute idée ou suggestion serait donc la bienvenue.
EDIT :
J'ai eu l'occasion de jouer un peu plus avec cela et j'ai trouvé une autre solution, mais elle ne semble pas beaucoup plus rapide que de choisir au hasard des cellules dans la matrice. Je dois également ajouter que la matrice doit être échangée environ 30000 fois de suite pour qu'elle soit considérée comme aléatoire et que je dois générer 5000 matrices aléatoires pour chaque test, dont au moins 5000 autres à faire, donc les performances sont importantes.
La solution actuelle (outre la sélection aléatoire des cellules) est :
- Sélectionnez aléatoirement 2 lignes de la matrice.
- Soustraire une ligne de l'autre et la mettre dans un tableau.
- si le nouveau tableau contient à la fois un 1 et un -1, alors nous pouvons permuter
La logique de la soustraction ressemble à ceci :
0 1 0 0
- 1 0 0 1
---------------
-1 1 0 -1
La méthode qui fait cela ressemble à ceci :
def findSwaps(matrix: Matrix, iterations: Int): Boolean = {
var result = false
val mtxLength = matrix.length
val row1 = rnd.nextInt(mtxLength)
val row2 = getNextIndex(row1, mtxLength)
val difference = subRows(matrix(row1), matrix(row2))
if (difference.min == -1 & difference.max == 1) {
val zeroOne = difference.zipWithIndex.filter(_._1 == -1).map(_._2)
val oneZero = difference.zipWithIndex.filter(_._1 == 1).map(_._2)
val col1 = zeroOne(rnd.nextInt(zeroOne.length))
val col2 = oneZero(rnd.nextInt(oneZero.length))
swap(matrix, row1, row2, col1, col2)
result = true
}
result
}
La soustraction des lignes de la matrice ressemble à ceci :
def subRows(a: Array[Int], b: Array[Int]): Array[Int] = (a, b).zipped.map(_ - _)
Et l'échange réel ressemble à ça :
def swap(matrix: Matrix, row1: Int, row2: Int, col1: Int, col2: Int) = {
val temp = (matrix(row1)(col1), matrix(row1)(col2))
matrix(row1)(col1) = matrix(row2)(col1)
matrix(row1)(col2) = matrix(row2)(col2)
matrix(row2)(col1) = temp._1
matrix(row2)(col2) = temp._2
matrix
}
Cela fonctionne beaucoup mieux qu'avant, car j'obtiens entre 80 et 90 % de réussite pour une tentative d'échange (il n'y en avait que 10 % avec la sélection aléatoire des cellules), mais... il faut toujours environ 2,5 minutes pour générer 1000 matrices aléatoires.
Des idées sur la façon d'améliorer la vitesse ?