Моя проблема очень проста, но я пока не нашел эффективной реализации.Как найти наиболее эффективные прямоугольные области с одинаковым значением для данного размера в матрице?
Предположим, что существует матрица А как это:
0 0 0 0 0 0 0
4 4 2 2 2 0 0
4 4 2 2 2 0 0
0 0 2 2 2 1 1
0 0 0 0 0 1 1
Теперь я хочу, чтобы найти все стартовые позиции прямоугольных областей в этой матрице, которые имеют заданный размер. Область является подмножеством A, где все числа одинаковы.
Предположим, ширина = 2 и высота = 3. Есть 3 области, которые имеют этот размер:
2 2 2 2 0 0
2 2 2 2 0 0
2 2 2 2 0 0
В результате вызова функции будет представлять собой список стартовых позиций (х, у, начиная с 0) из этих областей.
List((2,1),(3,1),(5,0))
Ниже приводится моя текущая реализация. «Районы» здесь называются «поверхностями».
case class Dimension2D(width: Int, height: Int)
case class Position2D(x: Int, y: Int)
def findFlatSurfaces(matrix: Array[Array[Int]], surfaceSize: Dimension2D): List[Position2D] = {
val matrixWidth = matrix.length
val matrixHeight = matrix(0).length
var resultPositions: List[Position2D] = Nil
for (y <- 0 to matrixHeight - surfaceSize.height) {
var x = 0
while (x <= matrixWidth - surfaceSize.width) {
val topLeft = matrix(x)(y)
val topRight = matrix(x + surfaceSize.width - 1)(y)
val bottomLeft = matrix(x)(y + surfaceSize.height - 1)
val bottomRight = matrix(x + surfaceSize.width - 1)(y + surfaceSize.height - 1)
// investigate further if corners are equal
if (topLeft == bottomLeft && topLeft == topRight && topLeft == bottomRight) {
breakable {
for (sx <- x until x + surfaceSize.width;
sy <- y until y + surfaceSize.height) {
if (matrix(sx)(sy) != topLeft) {
x = if (x == sx) sx + 1 else sx
break
}
}
// found one!
resultPositions ::= Position2D(x, y)
x += 1
}
} else if (topRight != bottomRight) {
// can skip x a bit as there won't be a valid match in current row in this area
x += surfaceSize.width
} else {
x += 1
}
}
}
return resultPositions
}
Я уже пытался включить в него некоторые оптимизации, но я уверен, что есть гораздо лучшие решения. Существует ли для этого функция matlab, которую я мог бы переносить? Мне также интересно, имеет ли эта проблема свое имя, поскольку я точно не знал, для чего нужно google.
Спасибо, что подумали! Я рад видеть ваши предложения или решения :)
EDIT: Размеры матрицы в моем диапазоне применения от 300x300 до 3000x3000 приблизительно. Кроме того, алгоритм будет называться только для той же матрицы. Причина в том, что матрица всегда будет изменена впоследствии (примерно 1-20% от нее).
РЕЗУЛЬТАТЫ
Я реализовал алгоритмы Кевина, Никита и Даниил и протестировал их в своей среде приложений, т.е. нет изолированных синтетических тестов здесь, но особое внимание было уделено интеграцией всех алгоритмов в наиболее производительном способе, который был особенно важно для подхода Кевина, поскольку он использует дженерики (см. ниже).
Во-первых, необработанные результаты, используя Scala 2.8 и jdk 1.6.0_23. Алгоритмы выполнялись несколько сотен раз как часть решения конкретной задачи. «Длительность» означает общее время, необходимое для завершения алгоритма приложения (конечно, без запуска jvm и т. Д.). Моя машина представляет собой Core 2 Duo 2,8 ГГц с 2 ядрами и 2gig памяти, -Xmx800M были переданы JVM.
ВАЖНОЕ ЗАМЕЧАНИЕ: Я думаю, что моя эталонная установка не очень справедлива для распараллеливаемых алгоритмов, подобных тому, что у Даниила. Это связано с тем, что приложение уже вычисляет многопоточность. Таким образом, результаты здесь, вероятно, показывают только эквивалентную однопоточную скорость.
Размер матрицы 233x587:
duration | JVM memory | avg CPU utilization
original O(n^4) | 3000s 30M 100%
original/-server| 840s 270M 100%
Nikita O(n^2) | 5-6s 34M 70-80%
Nikita/-server | 1-2s 300M 100%
Kevin/-server | 7400s 800M 96-98%
Kevin/-server** | 4900s 800M 96-99%
Daniel/-server | 240s 360M 96-99%
** с @specialized, чтобы сделать generics faster избегая Erasure
отсутствует Типразмер матрицы 2000x3000:
duration | JVM memory | avg CPU utilization
original O(n^4) | too long 100M 100%
Nikita O(n^2) | 150s 760M 70%
Nikita/-server | 295s (!) 780M 100%
Kevin/-server | too long, didn't try
Во-первых, небольшую заметку на память , Опция -server JVM использует значительно больше памяти в интересах большего количества оптимизаций и, как правило, более быстрого выполнения. Как видно из второй таблицы, алгоритм Никиты работает медленнее с опцией -server, что, очевидно, связано с ограничением памяти. Я предполагаю, что это также замедляет алгоритм Кевина даже для маленькой матрицы, так как функциональный подход все равно использует гораздо больше памяти. Чтобы устранить фактор памяти, я также попробовал его один раз с матрицей 50x50, а затем Кевин взял 5 секунд и 0 секунд на Nikita (ну, почти 0). Поэтому в любом случае он все еще медленнее, а не только из-за памяти.
Как видно из цифр, я, очевидно, буду использовать алгоритм Никиты, потому что это чертовски быстро, и это абсолютно необходимо в моем случае. Он также может быть легко распараллелен, как указал Даниил. Единственным недостатком является то, что на самом деле это не scala-way.
В настоящий момент алгоритм Кевина, вероятно, в целом слишком сложный и, следовательно, медленный, но я уверен, что возможны дополнительные оптимизации (см. Последние комментарии в его ответе).
С целью прямого преобразования алгоритма Никиты в функциональный стиль Даниэль придумал решение, которое уже довольно быстро, и, по его словам, будет даже быстрее, если он сможет использовать scanRight (см. Последние комментарии в его ответе).
Что дальше?
С технологической стороны: ожидание Scala 2.9, ScalaCL и выполнение синтетических тестов для получения исходных скоростей.
Моя цель во всем этом состоит в том, чтобы иметь функциональный код, НО только если он не жертвует слишком большой скоростью.
Выбор ответа:
Что касается выбора ответа, я хотел бы отметить алгоритмы Никиту и Даниила как ответы, но я должен выбрать один. Название моего вопроса включало «наиболее эффективно», а другое - самое быстрое в императиве, а другое - в функциональном стиле. Хотя этот вопрос отмечен Scala, я выбрал императивный алгоритм Никиты, поскольку 2s против 240s по-прежнему слишком большой разницы для меня. Я уверен, что разницу можно еще немного оттолкнуть, какие-нибудь идеи?
Итак, спасибо вам всем очень много! Хотя я не буду использовать функциональные алгоритмы еще, у меня появилось много новых знаний о Scala, и я думаю, что я медленно понимаю все функциональные сумасшествия и его потенциал. (конечно, даже не делая много функционального программирования, Scala гораздо приятнее, чем Java ...это еще одна причина, чтобы узнать его)
Есть несколько алгоритмов, чтобы найти регионы (например, в программе Paint): [заливка] (http://en.wikipedia.org/wiki/Flood_fill) [ Извлечение региона] (http://en.wikipedia.org/wiki/Connected_Component_Labeling). Но они не налагают прямоугольный узор. Ответ Кевина очень хорош для этого варианта использования. – rds
@neo, просто для любопытства, насколько велики матрицы, которые вам нужно обработать? –
@Paul в диапазоне от ок. От 300x300 до 3000x3000, поэтому я действительно добился наиболее эффективного алгоритма. Я тоже заинтересован в ScalaCL, но, к сожалению, моя карта gfx слишком стар для этого ... – letmaik