Моя проблема очень проста, но я еще не нашел эффективной реализации.
Предположим, что имеется такая матрица A:
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
Результатом вызова функции будет список начальных позиций (x, y, начиная с 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: Размеры матрицы в моем приложении варьируются от 300 x 300 до 3000 x 3000 примерно. Кроме того, алгоритм будет вызываться один раз для одной и той же матрицы. Причина в том, что матрица всегда будет потом меняться (примерно 1-20%).
ПОЛУЧЕННЫЕ РЕЗУЛЬТАТЫ
Я реализовал алгоритмы Кевина, Никиты и Даниэля и протестировал их в своей прикладной среде, т.е. здесь нет изолированного синтетического теста, но особое внимание было уделено интеграции всех алгоритмов наиболее эффективным способом, что было особенно важно для подхода Кевина, поскольку он использует дженерики. (Смотри ниже).
Во-первых, необработанные результаты с использованием Scala 2.8 и jdk 1.6.0_23. Алгоритмы выполнялись несколько сотен раз в рамках решения прикладной задачи. «Продолжительность» обозначает общее время, необходимое для завершения алгоритма приложения (конечно, без запуска jvm и т. д.). Моя машина - это Core 2 Duo 2,8 ГГц с 2 ядрами и 2 ГБ памяти, -Xmx800M были отданы JVM.
ВАЖНОЕ ЗАМЕЧАНИЕ: я думаю, что моя установка теста не совсем справедлива для параллельных алгоритмов, таких как тот, что предложил Даниэль. Это связано с тем, что приложение уже выполняет многопоточные вычисления. Таким образом, результаты здесь, вероятно, показывают только скорость, эквивалентную однопоточной.
Размер матрицы 233х587:
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, чтобы ускорить универсальные шаблоны, избегая стирания типов
Размер матрицы 2000х3000:
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 использует значительно больше памяти за счет большего количества оптимизаций и, как правило, более быстрого выполнения. Как видно из 2-й таблицы, алгоритм Никиты работает медленнее с параметром -server, что, очевидно, связано с превышением лимита памяти. Я предполагаю, что это также замедляет алгоритм Кевина даже для небольшой матрицы, поскольку функциональный подход в любом случае использует гораздо больше памяти. Чтобы устранить фактор памяти, я также попробовал это один раз с матрицей 50x50, и тогда у Кевина ушло 5 секунд, а у Никиты 0 секунд (ну, почти 0). Так что в любом случае все равно медленнее и не только из-за памяти.
Как видно из цифр, я явно буду использовать алгоритм Никиты, потому что он чертовски быстр, а в моем случае это совершенно необходимо. Его также можно легко распараллелить, как указал Дэниел. Единственным недостатком является то, что это не совсем скала-способ.
На данный момент алгоритм Кевина, вероятно, в целом слишком сложен и поэтому медленный, но я уверен, что возможны дополнительные оптимизации (см. Последние комментарии в его ответе).
С целью прямого преобразования алгоритма Никиты в функциональный стиль Даниэль придумал решение, которое уже довольно быстрое и, как он говорит, было бы даже быстрее, если бы он мог использовать scanRight (см. последние комментарии в его ответе).
Что дальше?
С технологической стороны: ожидание Scala 2.9, ScalaCL и выполнение синтетических тестов для получения сырых скоростей.
Моя цель во всем этом состоит в том, чтобы иметь функциональный код, НО только если он не слишком жертвует скоростью.
Выбор ответа:
Что касается выбора ответа, я хотел бы отметить алгоритмы Никиты и Даниэля как ответы, но я должен выбрать один. Заголовок моего вопроса включал «наиболее эффективно», и один из них был самым быстрым в императивном, а другой в функциональном стиле. Хотя этот вопрос помечен как Scala, я выбрал императивный алгоритм Никиты, так как разница между 2 и 240 с слишком велика, чтобы я мог ее принять. Я уверен, что разницу все еще можно немного уменьшить, есть идеи?
Итак, всем большое-пребольшое спасибо! Хотя я не буду использовать функциональные алгоритмы пока, я получил много новых знаний о Scala и думаю, что постепенно понимаю все функциональное сумасшествие и его потенциал. (конечно, даже не занимаясь функциональным программированием, Scala гораздо приятнее, чем Java... это еще одна причина ее изучать)
Int
илиDouble
, то одна ОГРОМНАЯ проблема скорости с кодом Кевина заключается в том, что он параметризован. Если вы пройдете его и заменитеT
фактическим типом, который вы используете (и удалите объявления параметров типа[T]
), вы получите гораздо более быстрый код. Вы можете сделать то же самое, посыпав код аннотациями@specialized
в нужных местах, что сделает код более быстрым для подклассовAnyVal
, но все же гибким. - person Daniel C. Sobral   schedule 15.01.2011T=(Int,Int)
- person Kevin Wright   schedule 15.01.2011