Алгоритм приближения прямоугольника

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

Есть ли лучший (то есть более читаемый и удобный) способ, чем спагетти-код, который я формулирую из множества вложенных if и else?

На данный момент у меня есть только:

enum imgOptsScale {
    //Some relative scales
    w005h005 = 0x8,
    w010h010 = 0x9,
    w020h020 = 0xA,
    w040h040 = 0xB,
    w070h070 = 0xC,
    w100h100 = 0xD,
    w150h150 = 0xE,
    w200h200 = 0xF,
    w320h320 = 0x10,
    w450h450 = 0x11,
    w200h010 = 0x12,
    w200h020 = 0x13,
    w200h070 = 0x14,
    w010h200 = 0x15,
    w020h200 = 0x16,
    w070h200 = 0x17
};
imgOptsScale getClosestSizeTo(int width, int height);

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


person John    schedule 10.02.2012    source источник
comment
Как вы определяете лучшее? Это что-то вроде евклидово расстояние по площади прямоугольника ? Или евклидово расстояние по сумме двух сторон? Или с обеих сторон по отдельности? Будет ли прямоугольник 6x9 идентичен прямоугольнику 9x6?   -  person sarnold    schedule 11.02.2012
comment
Никакие 6х9 не будут идентичны 9х6. Я представляю изображения, и масштабирование будет выглядеть ужасно в обратном порядке. Лучше всего, я пока не знаю, для относительного масштабирования я в основном масштабировал высоту, за исключением нескольких четвертей экрана и половины экрана.   -  person John    schedule 11.02.2012
comment
Я думаю, что мне нужно взять каждое измерение перечисления отдельно и убедиться, что все они присутствуют в моем перечислении в комбинации. Затем я могу просто аппроксимировать каждое измерение независимо. Где же эта формула для химической завивки и комбо?   -  person John    schedule 11.02.2012
comment
Если вы сделаете доступными все возможные комбинации ширины и высоты, просто получите отсортированный массив ширин и отсортированный массив высот, затем выполните двоичный поиск, чтобы найти наилучшую ширину, и еще один двоичный поиск, чтобы найти наилучшую высоту.   -  person tom    schedule 11.02.2012


Ответы (3)


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

Прочитайте массивы, чтобы найти следующий больший размер, и верните соответствующий ключ. Постройте окончательную меру коробки из двух ключей. (Поскольку 32 допускает только 5 бит, это, вероятно, не очень идеально — вы, вероятно, захотите 2,5 бита для горизонтального и 2,5 бита для вертикального, но мой простой подход здесь требует 6 бит — 3 для горизонтального и 3 для вертикального . Вы можете удалить половину элементов из одного из списков (и, возможно, также изменить << 3), если вас устраивает одно из измерений с меньшим количеством степеней свободы. Если вы хотите, чтобы оба измерения были лучше представлены, это, вероятно, будет требуют достаточной доработки, поэтому этот подход может оказаться неприемлемым.)

Непроверенный псевдокод:

struct dimen {
    int x;
    int key;
}

struct dimen horizontal[] = { { .x = 10, .key = 0 },
                              { .x = 20, .key = 1 },
                              { .x = 50, .key = 2 },
                              { .x = 90, .key = 3 },
                              { .x = 120, .key = 4 },
                              { .x = 200, .key = 5 },
                              { .x = 300, .key = 6 },
                              { .x = 10000, .key = 7 }};

struct dimen vertical[] = { { .x = 10, .key = 0 },
                           { .x = 20, .key = 1 },
                           { .x = 50, .key = 2 },
                           { .x = 90, .key = 3 },
                           { .x = 120, .key = 4 },
                           { .x = 200, .key = 5 },
                           { .x = 300, .key = 6 },
                           { .x = 10000, .key = 7 }};

/* returns 0-63 as written */
int getClosestSizeTo(int width, int height) {
    int horizontal_key = find_just_larger(horizontal, width);
    int vertical_key = find_just_larger(vertical, height);
    return (horizontal_kee << 3) & vertical_key;
}

int find_just_larger(struct dimen* d, size) {
    int ret = d.key;
    while(d.x < size) {
        d++;
        ret = d.key;
    }
    return ret;
}
person sarnold    schedule 11.02.2012
comment
Я рад, что вы согласны, что мне нужно 63, а не 32 перечисления. У меня все еще есть 8 бит в резерве, и все идет хорошо. - person John; 11.02.2012
comment
64 или 16 оба будут легче, чем 32; 32 определенно выполнимо с этим механизмом, но, возможно, с результатами ниже номинала. Я уверен, что есть более умный механизм для нечетных степеней двойки, но пока этого может быть достаточно. - person sarnold; 11.02.2012
comment
Просто верните pair<int, int>. И сделайте бинарный поиск вместо линейного сканирования. И забудьте о ключах — используйте простой массив int и возвращайте фактическое измерение вместо ключа измерения. - person tom; 11.02.2012
comment
@tom: pair<int,int> далеко за пределами моих навыков C++, и лучше оставить это кому-то другому. Для поиска восьми элементов двоичный поиск требует в среднем примерно три поиска, а линейный поиск требует в среднем примерно четыре поиска. Угадайте, что проще реализовать? :) key является частью построения типа imgOptsScale, который он хотел - он не просил размеры, а магический идентификатор. Индекс массива также может выполнять эту работу - определенно будет чище... - person sarnold; 11.02.2012
comment
@sarnold: Очень важные моменты. Я забираю свой комментарий (но оставлю его там для протокола). - person tom; 11.02.2012
comment
@tom: особенно стоит оставить предложение pair<int,int>. Спасибо! :) - person sarnold; 11.02.2012

Да... поместите свои 32 различных размера в предварительно построенное двоичное дерево поиска, а затем рекурсивно выполните поиск по дереву "наилучшего" размера. По сути, вы остановите свой поиск, если левый дочерний предварительно построенный прямоугольник прямоугольника текущего узла меньше, чем ваш входной прямоугольник, а прямоугольник текущего узла больше, чем входной прямоугольник. Затем вы вернете предварительно определенный прямоугольник, который является «ближайшим» к вашему входному прямоугольнику между ними.

Одним приятным дополнением к чистому коду, создаваемому рекурсивным поиском, является то, что он также будет логарифмическим, а не линейным по времени поиска.

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

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

//for brevity, find the rectangle that is the 
//greatest rectangle smaller than the input
const rec_bstree* find_best_fit(const rec_bstree* node, const rec& input_rec)
{
    if (node == NULL)
        return NULL;

    rec_bstree*  return_node;

    if (input_rec.area < node->area)
        return_node = find_best_fit(node->left_child, input_rec);
    else if (input_rec.area > node->area)
        return_node = find_best_fit(node->right_child, input_rec);

    if (return_node == NULL)
        return node;
}

Кстати, если дерево слишком сложное, вы также можете просто создать массив или std::vector экземпляров ваших прямоугольников, отсортировать их с использованием некоторого типа критериев, используя std::sort, а затем выполнить двоичный поиск в массиве.

person Jason    schedule 10.02.2012
comment
Я знаком с преимуществами бинарной сортировки, но до сих пор не понимаю, как я могу применить ее, увидев, меньше ли прямоугольник, чем мой входной прямоугольник, конечно, это лучше подходит, чем ближайшее приближение? Поиск ближайшего потребует измерения каждого измерения, а не сравнения прямоугольников. Даже имея всего 24 прямоугольника, чтобы сравнить спагетти if-else с сотнями строк кода! - person John; 11.02.2012
comment
Есть много способов сортировки... Я опубликовал очень простую версию для краткости, но вы могли бы, если хотите, выполнить лексическую сортировку или отсортировать по взвешенному значению площади, длины и ширины. и т.д. Возможностей масса... - person Jason; 11.02.2012

Вот мое предлагаемое решение,

enum imgOptsScale {
    notScaled = 0x0,
    //7 relative scales upto = 0x7
    w010h010, w010h025, w010h060, w010h120, w010h200, w010h310, w010h450,
    w025h010, w025h025, w025h060, w025h120, w025h200, w025h310, w025h450,
    w060h010, w060h025, w060h060, w060h120, w060h200, w060h310, w060h450,
    w120h010, w120h025, w120h060, w120h120, w120h200, w120h310, w120h450,
    w200h010, w200h025, w200h060, w200h120, w200h200, w200h310, w200h450,
    w310h010, w310h025, w310h060, w310h120, w310h200, w310h310, w310h450,
    w450h010, w450h025, w450h060, w450h120, w450h200, w450h310, w450h450,
    w730h010, w730h025, w730h060, w730h120, w730h200, w730h310, w730h450
};
//Only call if width and height are actually specified. else 0=>10px 
imgOptsScale getClosestSizeTo(int width, int height) {
    static const int possSizes[] = {10, 25, 60, 120, 200, 310, 450, 730};
    static const int sizesHalfways[] = {17, 42, 90, 160, 255, 380, 590};
    int widthI = 6;
    while (sizesHalfways[widthI - 1] > width && --widthI>0);
    int heightI = 6;
    while (sizesHalfways[heightI - 1] > height && --heightI>0);
    return (imgOptsScale)(8 + 7 * widthI + heightI);
}
person John    schedule 11.02.2012