Ищем ограниченный алгоритм перетасовки

У меня проблема с перетасовкой. Существует множество страниц и дискуссий о том, как полностью перетасовать массив значений, как стопку карт.

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

То есть если N равно 2, то элемент I будет перетасован не более чем на позицию от I-2 до I+2 (в пределах массива).

Это оказалось сложным с некоторыми простыми решениями, приводящими к смещению направления движения элемента или к неравномерной величине.


person anthony    schedule 20.08.2015    source источник
comment
Мне любопытно, откуда вы это придумали или что заставило вас думать, что вам нужно такое поведение. Это поставило меня и моих коллег в тупик, так что спасибо за это!   -  person dimo414    schedule 25.08.2015
comment
На самом деле проблема с графикой. Посмотрите на команду ppmspread или параметр преобразования ImageMagick -spread. В обоих случаях источник подразумевает, что он меняет местами пиксели и, таким образом, сохраняет все пиксели исходного изображения, только что смещенные. Однако... Это не так, оба оператора изображений фактически теряют информацию, а пиксели дублируются и теряются. Я хотел исправить этот «наивный» подход, но простая замена пикселей при работе с изображением приводит к тому, что некоторые пиксели становятся «двойной заменой». Поиски решения не увенчались успехом.   -  person anthony    schedule 27.08.2015
comment
Обратите внимание, что если N имеет тот же размер (или больше) фактического массива, то перетасовка должна по существу перейти к полной перетасовке массива. Хотя при обычном использовании N должно быть довольно маленьким относительно длины массива. Обычно от 1 до 5.   -  person anthony    schedule 27.08.2015
comment
Как вы думаете, это ошибка в ppmspread/spread? Вы смотрели исходный код этих операций? Ссылки на документацию, которую вы просматриваете, были бы полезны, поэтому мы уверены, что рассматриваем одни и те же вещи.   -  person dimo414    schedule 27.08.2015
comment
Исходный исходный код этой обработки изображений говорит, что они перетасовываются, и ppmspread даже сталкивается с проблемой замены обоих элементов, но они только изменяют целевое изображение, оставляя исходное изображение как есть. Это означает, что по мере того, как он проходит через элементы, которые были «заменены местами» до текущей точки обработки, перезаписываются более поздним «свопом». Вы можете увидеть, как теряются данные пикселей, если вы обрабатываете градиентное изображение, где каждый пиксель уникален и находится в последовательности заранее. ссылка   -  person anthony    schedule 31.08.2015


Ответы (2)


Вы правы, это сложно! Во-первых, нам нужно установить еще несколько правил, чтобы гарантировать, что мы не создадим искусственно неслучайные результаты:

  • Элементы можно оставить в той позиции, с которой они начинались. Это необходимая часть любой честной перетасовки, а также гарантирует, что наша перетасовка будет работать при N=0.
  • Когда N больше, чем расстояние элемента от начала или конца массива, его можно переместить на другую сторону. Мы могли бы настроить алгоритм, чтобы запретить это, но это нарушило бы требование «равномерности» — элементы, расположенные ближе к краям, с большей вероятностью останутся на месте, чем элементы, расположенные ближе к середине.

Теперь мы действительно можем решить проблему.

  1. Создайте массив случайных значений в диапазоне i + [-N, N], где i — текущий индекс в массиве. Нормализация значений за пределами массива (например, -1 должно стать length-1, а length должно стать 0).
  2. Look for pairs of duplicate values (collisions) in the array, and recompute them. You have a few options:
    • Recompute both values until they don't collide with each other, they could both still collide with other values.
    • Перевычисляйте только одно, пока оно не перестанет сталкиваться с другим, первое значение все еще может сталкиваться, но второе должно теперь быть уникальным, что может означать меньше вызовов ГСЧ.
    • Определите набор доступных индексов для каждого столкновения (например, в [3, 1, 1, 0] доступен индекс 2), выберите случайное значение из этого набора и установите одно из значений массива для выбранного результата. Это позволяет избежать необходимости зацикливаться до тех пор, пока коллизия не будет разрешена, но более сложно кодировать и есть риск столкнуться с ситуацией, когда множество пусто.
  3. Как бы вы ни обращались к отдельным коллизиям, повторяйте процесс, пока каждое значение в массиве не станет уникальным.
  4. Теперь переместите каждый элемент исходного массива в индекс, указанный в сгенерированном нами массиве.

Я не уверен, как лучше всего реализовать № 2, я бы посоветовал вам сравнить его. Если вы не хотите тратить время на тестирование, я бы выбрал первый вариант. Остальные — это оптимизации, которые могут работать быстрее, но на самом деле могут оказаться медленнее.

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

person dimo414    schedule 20.08.2015
comment
Подводя итог: создайте отдельный массив рандомизированных индексов в диапазоне +/- N текущего индекса. Затем найдите разрешать повторяющиеся индексы. С этой позиции этот процесс урегулирования может быть трудным, если не невозможным. - person anthony; 21.08.2015
comment
Третий вариант (вычисление набора доступных индексов) будет выполняться за ограниченное время, хотя я не уверен, что он на самом деле лучше, чем неограниченные варианты в общем случае. Как я уже сказал, вам нужно будет сравнить его. Если решение оказывается невозможным, возможно, ваша проблема не имеет общего решения. - person dimo414; 24.08.2015

Я придумал одно возможное решение, хотя насколько оно «наивно», я не уверен. Особенно по краям, особенно дальний край.

  1. создать массив флагов (логический) длиной N (представляющий элементы, которые были заменены местами)

  2. Для проверки каждого индекса, если он уже был заменен (согласно первому элементу в массиве флагов), если это так, перейдите к следующему (см. ниже)

  3. поверните массив флагов, удалив первый элемент (представляющий этот элемент), и добавьте в конец новый элемент «не заменен». ВНИМАНИЕ: это можно сделать с помощью поиска массива модулей, чтобы избежать фактического перемещения содержимого массива, особенно для больших N

  4. Петля...

    • pick a number from 0 to N (or less than N, if N plus current index is larger that array being shuffled.
    • Если 0, элемент меняет местами сам с собой, переходит к следующему.
    • В противном случае, если этот элемент помечен как замененный, повторите цикл и повторите попытку. Обратите внимание, что в массиве флагов всегда есть 2 элемента, которые можно выбрать, сам и последний элемент (если он не находится близко к концу перетасовываемого массива).
  5. Поменять местами текущий элемент с выбранным непереставленным элементом, пометить выбранный элемент как замененный в массиве флагов. Перейти к следующему элементу

person anthony    schedule 26.08.2015
comment
Попробую перефразировать, чтобы понять. Вы предлагаете перебирать каждый элемент по очереди, определяя правильную позицию для его размещения, а затем повторяя попытку, если эта позиция уже занята. Это правильно? Если я не ошибаюсь, это не будет равномерным, потому что элементы ближе к концу списка будут иметь меньше доступных позиций, чем те, что в начале. - person dimo414; 27.08.2015
comment
Только если их уже не поменяли местами. В конце элементы маркировки массива, которые не были перемешаны, будут уменьшаться. Хммм, если вы подумаете об этом со значением N, равным или большим, чем размер массива, он перейдет к перетасовке Фишера-Йейтса, но без возможности повторного перетасовки элементов, если они ранее перетасовывались. Для меня это должно быть единообразным. - person anthony; 31.08.2015