У меня есть N прямоугольников одинакового размера rectWidth*rectHeight. У меня есть область с размерами areaWidth*areaHeight. Я хочу разместить N прямоугольников в области, сохраняя соотношение сторон прямоугольников, изменяя размер прямоугольников, чтобы они подходили. Между прямоугольниками я хочу пробел.

Каковы должны быть размеры прямоугольников, чтобы все они поместились внутри прямоугольника и сохраняли соотношение сторон?

5
Serge van den Oever 30 Янв 2015 в 02:29
2
Вам нужно будет указать это немного лучше. Прямо сейчас проблема очень слабо ограничена: я могу просто изменить размер всех прямоугольников на 0x0. Вы хотите конкретное место? Должны ли они все быть изменены с одинаковым коэффициентом?
 – 
Thomas
30 Янв 2015 в 02:39
Привет Томас, спасибо за ваш ответ. Размещение не проблема, я передаю прямоугольники элементу управления, который их размещает. Все N прямоугольников имеют одинаковые размеры и должны сохранять те же размеры в новой ситуации. Я хочу, чтобы они были исходного размера (rectWidth*rectHeight) или меньше, если они не все подходят. Результат алгоритма должен быть фактором, с помощью которого можно изменить их размер, чтобы они подходили. Если этот коэффициент > 1, я оставляю все как есть, если коэффициент < 1, я изменяю размер всех прямоугольников на этот коэффициент. Я надеюсь, что это объясняет требования и ограничения достаточно хорошо.
 – 
Serge van den Oever
30 Янв 2015 в 11:00
Я считаю, что вопрос следует перефразировать так: каковы должны быть МАКСИМАЛЬНЫЕ размеры прямоугольников, чтобы они все помещались в прямоугольник и сохраняли соотношение сторон?
 – 
31415926
31 Янв 2015 в 19:10
Я попробовал подход с прямой формулой, который говорит мне, что a должно быть близко к NR/r или b близко к Nr/R, где R — соотношение сторон вашей области, а r и N — соотношение сторон и количество прямоугольников соответственно, а и b количество прямоугольников в ширине и высоте области соответственно. Прежде чем я приму во внимание интервалы, может ли кто-нибудь найти конфигурацию, которая дает неправильные результаты с этим jsfiddle.net/ohbhy4uw?
 – 
Cimbali
4 Фев 2015 в 04:03

2 ответа

Пусть есть N прямоугольников.
Пусть прямоугольники имеют размер (cw, ch), где 0 < c ≤ 1.
Пусть область, в которую вы хотите вписаться, имеет размер (W, H).
Пусть s ≥ 0 — расстояние между прямоугольниками.

Горизонтальный размер a > 0 прямоугольников, сложенных горизонтально, равен acw + (a - 1)s.
Мы знаем, что acw + (a - 1)s ≤ W.

Вертикальный размер b > 0 прямоугольников, сложенных вертикально, равен bch + (b - 1)s.
Мы знаем, что bch + (b - 1)s ≤ H.

Тогда мы имеем следующую задачу оптимизации.

Макс. с
при условии
a ≤ (W + s) / (cw + s)
б ≤ (Н + с) / (кан + с)
аб ≥ N
0 < с ≤ 1
a, b > 0 и целое число

Теперь рассмотрим следующие неравенства.

A ≤ (W + s) / (cw + s)
б ≤ (Н + с) / (ч + с)

Любое оптимальное решение должно превращать хотя бы одно из этих неравенств в строгое неравенство.
То есть для оптимального решения (a, b, c) выполняется по крайней мере одно из следующих утверждений.

A = (W + s) / (cw + s) ↔ c = (W - s(a - 1)) / wa
b = (H + s) / (ch + s) ↔ c = (H - s (b - 1)) / wb

Предположим без ограничения общности, что выполняется a = (W + s) / (cw + s).
Поскольку a должен принимать одно из значений в {1, 2, ..., N},
c должен принимать одно из значений {W / w, (W - s) / 2w, (W - 2s) / 3w, ..., (W - (N - 1)s) / Nw}.

Аналогичные рассуждения дают список значений c, которые должны быть получены в случае, когда второе неравенство (для b) является точным.

Если вы объедините эти два списка значений, у вас будет не более 2N возможных значений, которые c может принять в оптимальном решении. Отсортируйте эти значения, затем выполните двоичный поиск максимального c в этом списке, для которого допустимы значения a и b.

Способ проверить допустимость значения c состоит в том, чтобы установить

A = пол((W + s) / (cw + s))
b = пол ((H + s) / (ch + s))

А затем проверить, что ab ≥ N.

5
Timothy Shields 3 Фев 2015 в 02:21
Это выглядит блестяще... но нужно изучить это :-) Мы (Pi=31415926 & I) попытаемся преобразовать это в функцию JavaScript :-)
 – 
Serge van den Oever
2 Фев 2015 в 16:41
Хм... так что мне нужно сделать 2 цикла над N элементами, чтобы собрать все возможные значения c. Насколько это быстрее, чем мое решение, в котором я повторяю меньше N раз? Я собираюсь добавить некоторые пояснения к моему ответу...
 – 
31415926
2 Фев 2015 в 17:28
Проблема с методом, который вы разместили, заключается в том, что он не всегда будет работать.
 – 
Timothy Shields
2 Фев 2015 в 18:13
1
Из комментариев к вопросу: "Я хочу, чтобы они были их исходного размера (rectWidth*rectHeight) или меньше, если они не все помещаются". Вы становитесь немного глупее с критикой , так что я воздержусь от дальнейшего обсуждения.
 – 
Timothy Shields
3 Фев 2015 в 02:22
1
И ваш алгоритм, и этот абсолютно одинаковы, если бы не предположение, что c <= 1 и тот факт, что вы проверяете выполнимость ранее (т.е. в первом цикле). Вы оба предполагаете, что одно направление будет упаковано (без свободного места, кроме «желобов»), перебираете все количество возможных прямоугольников в каждом направлении, берете максимально возможное значение, а затем берете максимальный результат в обоих направлениях. Независимо от того, выполняете ли вы два цикла O (n) и один цикл O (2n), как Тимоти, или два цикла O (n) с вдвое большим количеством инструкций, как вы, это строго одно и то же.
 – 
Cimbali
3 Фев 2015 в 02:58

Как насчет этого решения JavaScript?

var areaHeight = window.innerHeight;   //set here your area height
var areaWidth = window.innerWidth;     //set here your area width
var N = 216;                           //set amount of rectangles you want to fit
var rectRatio = 9/4;                   //set rectangle ratio
var gutter = [5, 10];                  //set x and y spacing between rectangles
var cols, rows, rectHeight, rectWidth; //variables that we need to calculate

Функция предполагает, что сетка прямоугольников (холст) всегда будет соответствовать высоте области контейнера. Вы передаете количество строк функции, и она вычисляет размер прямоугольника и определяет, больше ли ширина холста ширины контейнера или нет. Если холст больше, мы делаем row++ и снова вызываем функцию.

function rowIterator(iterator) {
   rows = iterator;
   cols = Math.ceil(N/rows);  

   rectHeight = (areaHeight - (rows-1)*gutter[1])/rows;          
   rectWidth = rectHeight*rectRatio;

   if (cols * rectWidth + (cols - 1)*gutter[0] > areaWidth) {
       rowIterator(rows + 1);
   }
}

rowIterator(1);                       //feed initial value
var size1 = [rectWidth, rectHeight];

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

function colIterator(iterator) {
   cols = iterator;
   rows = Math.ceil(N/cols);

   rectWidth = (areaWidth - (cols - 1)*gutter[0])/cols;
   rectHeight = rectWidth/rectRatio;

   if (rows * rectHeight + (rows - 1)*gutter[1] > areaHeight) {
       colIterator(cols + 1);
   }
}
colIterator(1);
var size2 = [rectWidth, rectHeight];

Общее количество итераций для обоих итераторов будет около N, максимальный размер прямоугольника:

optimalRectSize = [Math.max(size1[0], size2[0]), Math.max(size1[1], size2[1])]
2
31415926 3 Фев 2015 в 01:22