Scala-версия алгоритма обмена для нулевых моделей

Проблема, с которой я столкнулся, заключается в попытке найти эффективный способ поиска заменяемых элементов в матрице, чтобы реализовать алгоритм подкачки для создания нулевой модели.

Матрица состоит из 0 и 1, и идея заключается в том, что элементы можно переключать между столбцами, чтобы итоговые значения строки и столбца матрицы оставались неизменными.

Например, дана следующая матрица:

   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

каждый столбец c2 и c4 в r1 и r2 можно поменять местами таким образом, чтобы итоговые значения не были изменены, т.е.

   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

Все это должно быть сделано случайным образом, чтобы не вносить никакого смещения.

У меня есть одно решение, которое работает. Я случайным образом выбираю строку и два столбца. Если они дают шаблон 10 или 01, тогда я случайным образом выбираю другую строку и проверяю те же столбцы, чтобы увидеть, дают ли они противоположный шаблон. Если любой из них не удается, я начинаю заново и выбираю новый элемент.

Этот метод работает, но я только "ударил" правильные паттерны примерно в 10% случаев. В большой матрице или в одной с несколькими единицами в строках я теряю много времени, "пропуская". Я подумал, что должен быть более разумный способ выбора элементов в матрице, но все же делать это случайным образом.

Код для рабочего метода:

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)
  }
}

Я подумал, что более эффективный способ справиться с этим - удалить все строки, которые нельзя использовать (все 1 или 0), а затем выбрать элемент случайным образом. Оттуда я мог отфильтровать любые столбцы в строке, которые имели такое же значение и выбрать из оставшихся столбцов.

После выбора первой строки и столбца я отфильтровываю строки, которые не могут обеспечить требуемый шаблон, а затем выбираю из оставшихся строк.

Это работает по большей части, но проблема, с которой я не могу понять, что делать, это то, что происходит, когда нет столбцов или строк на выбор? Я не хочу бесконечно зацикливаться, пытаясь найти нужный мне шаблон, и мне нужен способ начать сначала, если я получу пустой список строк или столбцов на выбор.

Код, который у меня есть на данный момент, работает (пока я не получу пустой список):

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))
}

Я думаю, что рекурсивные методы неправильны и не работают здесь. Кроме того, я действительно просто пытаюсь улучшить скорость выбора ячеек, поэтому любые идеи или предложения будут с благодарностью.

РЕДАКТИРОВАТЬ:

У меня была возможность поиграть с этим немного больше, и я нашел другое решение, но оно, кажется, не намного быстрее, чем просто случайный выбор ячеек в матрице. Кроме того, я должен добавить, что матрицу нужно менять местами примерно 30000 раз подряд, чтобы ее можно было считать рандомизированной, и мне нужно сгенерировать 5000 случайных матриц для каждого теста, для которого у меня есть как минимум еще 5000, чтобы производительность была хорошей. важно.

Текущее решение (кроме случайного выбора ячейки:

  1. Случайно выбрать 2 строки из матрицы
  2. вычтите один ряд из другого и поместите его в массив
  3. если новый массив содержит как 1, так и -1, то мы можем поменять местами

Логика вычитания выглядит так:

  0  1  0  0
- 1  0  0  1
---------------
 -1  1  0 -1

Метод, который делает это выглядит следующим образом:

 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
 }

Вычитание строки матрицы выглядит следующим образом:

 def subRows(a: Array[Int], b: Array[Int]): Array[Int] = (a, b).zipped.map(_ - _)

И фактический обмен выглядит следующим образом:

 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
 }

Это работает намного лучше, чем раньше, потому что я получаю от 80% до 90% успеха при попытке перестановки (это было только около 10% при случайном выборе ячеек), однако... это все еще занимает около 2,5 минут, чтобы сгенерировать 1000 рандомизированных матрицы.

Любые идеи о том, как улучшить скорость?

1 ответ

Решение

Я собираюсь предположить, что матрицы большие, поэтому порядок хранения (размер матрицы в квадрате) нежизнеспособен (по соображениям скорости или памяти).

Если у вас есть разреженная матрица, вы можете ввести индекс каждого 1 в каждом столбце в наборе (здесь я покажу компактный способ сделать что-то, но вы можете использовать циклы while для скорости):

val mtx = Array(Array(0,1,0,0),Array(1,0,0,1),Array(0,0,0,0),Array(1,1,1,1))
val cols = mtx.transpose.map(x => x.zipWithIndex.filter(_._1==1).map(_._2).toSet)

Теперь для каждого столбца более поздний столбец содержит совместимые пары (хотя бы одну) тогда и только тогда, когда только следующие два набора непусты:

def xorish(a: Set[Int], b: Set[Int]) = (a--b, b--a)

Таким образом, ответ будет включать вычисление этих наборов и тестирование, являются ли они оба непустыми.

Теперь вопрос в том, что вы подразумеваете под "выборкой случайно". Случайная выборка одиночных 1,0 пар - это не то же самое, что случайная выборка возможных свопов. Чтобы увидеть это, рассмотрим следующее:

1 0       1 0
1 0       1 0
1 0       1 0
0 1       1 0
0 1       1 0
0 1       0 1

Два столбца слева имеют девять возможных перестановок. У двух справа есть только пять возможных обменов. Но если вы ищете (1,0) паттернов, вы будете выбирать только три раза слева против пяти справа; если вы ищете (1,0) или (0,1), вы выберете шесть и шесть, что снова искажает вероятности. Единственный способ исправить это - либо не быть умным, а случайным образом сделать выборку во второй раз (что в первом случае сработает с помощью полезного свопа в 3/5 времени, тогда как во втором - только в 1/5), или чтобы в основном вычислить каждую возможную пару для обмена (или, по крайней мере, сколько пар существует) и выбрать из этого предопределенного набора.

Если мы хотим сделать последнее, отметим, что для каждой пары неидентичных столбцов мы можем вычислить два набора, между которыми нужно поменяться, и мы знаем, что размеры и продукт - это общее количество возможностей. Чтобы не создавать все возможности, мы можем создать

val poss = {
  for (i<-cols.indices; j <- (i+1) until cols.length) yield 
    (i, j, (cols(i)--cols(j)).toArray, (cols(j)--cols(i)).toArray)
}.filter{ case (_,_,a,b) => a.length>0 && b.length>0 }

а затем посчитайте, сколько их:

val cuml = poss.map{ case (_,_,a,b) => a.size*b.size }.scanLeft(0)(_ + _).toArray

Теперь, чтобы выбрать случайное число, мы выбираем число от 0 до cuml.last и выбираем, что это за корзина и какой элемент в корзине:

def pickItem(cuml: Array[Int], poss: Seq[(Int,Int,Array[Int],Array[Int])]) = {
  val n = util.Random.nextInt(cuml.last)
  val k = {
    val i = java.util.Arrays.binarySearch(cuml,n)
    if (i<0) -i-2 else i
  }
  val j = n - cuml(k)
  val bucket = poss(k)
  (
    bucket._1, bucket._2, 
    bucket._3(j % bucket._3.size), bucket._4(j / bucket._3.size)
  )
}

Это заканчивается возвращением (c1,c2,r1,r2) выбран случайным образом.

Теперь, когда у вас есть координаты, вы можете создать новую матрицу по своему усмотрению. (Наиболее эффективным является, вероятно, сделать обмен записями на месте, а затем вернуться назад, когда вы захотите повторить попытку.)

Обратите внимание, что это имеет смысл только для большого количества независимых свопов из одной и той же исходной матрицы. Если вместо этого вы хотите сделать это итеративно и сохранить независимость, вам, вероятно, лучше всего делать это случайным образом, если только матрицы не очень разрежены, и в этот момент стоит просто хранить матрицы в некотором стандартном формате разреженных матриц (т. Е. По ненулевому индексу записей) и манипулирование ими (возможно, с изменяемыми наборами и стратегией обновления, так как последствия одного обмена ограничены примерно n из записей в n*n матрица).

Другие вопросы по тегам