Биты и байты: сохраните инструкцию перемешивания - PullRequest
1 голос
/ 11 марта 2011

Учитывая байтовый массив длиной два, у нас есть две возможности для перемешивания. 01 и 10

Длина 3 позволила бы эти варианты тасования 012, 021, 102, 120, 102, 201, 210. Всего 2х3 = 6 вариантов.

Длина 4 будет иметь 6x4 = 24. Длина 5 будет иметь 24x5 = 120 вариантов и т. Д.

Итак, как только вы случайно выбрали один из этих вариантов случайного воспроизведения, как вы его сохраните? Вы можете сохранить 23105, чтобы указать, как перетасовать четыре байта. Но это занимает 5x3 = 15 бит. Я знаю, что это может быть сделано в 7 битах, потому что есть только 120 возможностей.

Есть идеи, как более эффективно хранить инструкцию перемешивания? Это должен быть алгоритм, который будет масштабироваться по длине.

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

Ответы [ 5 ]

4 голосов
/ 11 марта 2011

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

Пример: Перемешивание 1 4 5: возможности:

1 4 5   [0]
1 5 4   [1]
4 1 5   [2]
4 5 1   [3]
5 1 4   [4]
5 4 1   [5]

Чтобы сохранить перестановку 415, вам нужно просто сохранить 2 (с нулями).

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

Однако попытка сгенерировать все перестановки одна за другой займет значительное время за пределами самых маленьких наборов. Вы можете использовать наблюдение, что первые (N-1)! перестановки начинаются с 1-го элемента, вторые (N-1)! перестановки начинаются со второго элемента, затем для каждой перестановки, которая начинается с определенного элемента, 1-я (N-2)! перестановка начинается с первый из оставшихся элементов и так далее и тому подобное. Это позволит вам «упаковать» или «распаковать» элементы в O(n), исключая сложность фактического создания факториалов и деления и модуля целых чисел произвольной длины, что будет несколько существенным.

3 голосов
/ 11 марта 2011

Вы правы в том, что для хранения только перестановки данных, а не самих данных, вам понадобится только столько бит, сколько указано в ceil (log2 (перестановки)).Для N элементов число перестановок является факториальным (N) или N !, поэтому вам понадобятся биты ceil (log2 (factorial (N))) для хранения только перестановки из N элементов без сохранения элементов.1002 * На любом языке, который вам знаком, должен быть готовый способ создать большой массив из M битов, заполнить его перестановкой и затем сохранить на устройстве хранения.

1 голос
/ 11 марта 2011

Для массива из L элементов, почему бы не упаковать порядок в биты L * ceil (log2 (L))? (ceil (log2 (L)) - количество битов, необходимых для хранения уникальных значений L.) L=2: 0 1 (2 bits) L=3: 00 01 10 (6 bits) L=4: 00 01 10 11 (8 bits) L=5: 000 001 010 011 100 (15 bits) ... L=8: 000 001 010 011 100 101 110 111 (24 bits) L=9: 0000 0001 0010 0011 0100 0101 0110 0111 1000 (36 bits) ... L=16: 0000 0001 ... 1111 (64 bits) L=128: 00000000 000000001 ... 11111111 (1024 bits) Основным преимуществом этой схемы по сравнению с ответом @ user470379 является то, что действительно легко извлечь индексы, просто сдвиг и маску. Не нужно регенерировать таблицу перестановок. Это должно быть большим выигрышем для большого L: (Для 128 предметов есть 128! = 3.8562e + 215 возможных перестановок).

(Перестановки == "возможности"; факториал = L! = L * (L-1) * ... * 1 = именно то, как вы рассчитываете возможности)

Этот метод также не намного больше, чем хранение индекса перестановки. Вы можете хранить 128 элементов в случайном порядке в 1024 битах (32 х 32-битных целых). Для хранения 128 требуется 717 бит (23 дюйма).

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


Вот реализация в Ruby, которая должна работать для произвольных размеров. «Инструкция перемешивания» содержится в массиве instruction. Первая часть вычисляет случайное использование, используя версию алгоритма Фишера-Йейтса, о которой @Theran упомянул

# Some Setup and utilities
sizeofInt = 32  # fix for your language/platform
N = 16
BitsPerIndex = Math.log2(N).ceil
IdsPerWord = sizeofInt/BitsPerIndex

# sets the n'th bitfield in array a to v
def setBitfield a,n,v
  mask = (2**BitsPerIndex)-1
  idx = n/IdsPerWord
  shift = (n-idx*IdsPerWord)*BitsPerIndex
  a[idx]&=~(mask<<shift)
  a[idx]|=(v&mask)<<shift
end

# returns the n'th bitfield in array a
def getBitfield a,n
  mask = (2**BitsPerIndex)-1
  idx = n/IdsPerWord
  shift = (n-idx*IdsPerWord)*BitsPerIndex
  return (a[idx]>>shift)&mask
end  

#create the shuffle instruction in linear time 
nwords = (N.to_f/IdsPerWord).ceil  # num words required to hold instruction
instruction = Array.new(nwords){0} # array initialized to 0

#the "inside-out" Fisher–Yates shuffle
for i in (1..N-1)
  j = rand(i+1)
  setBitfield(instruction,i,getBitfield(instruction,j))
  setBitfield(instruction,j,i)
end

#Here is a way to visualize the shuffle order
#delete ".reverse.map{|s|s.to_i(2)}" to visualize the way it's really stored
p instruction.map{|v|v.to_s(2).rjust(BitsPerIndex*IdsPerWord,'0').scan(
    Regexp.new('.'*BitsPerIndex)).reverse.map{|s|s.to_i(2)}}

Вот пример применения тасования к массиву символов:

A=(0...N).map{|v|('A'.ord+v).chr}
puts A*''

#Apply the shuffle to A in linear time
for i in (0...N)
 print A[getBitfield(instruction,i)]
end
print "\n"

#example: for N=20, produces
> ABCDEFGHIJKLMNOPQRST
> MSNOLGRQCTHDEPIAJFKB

Надеюсь, это не будет слишком сложно конвертировать в javascript или любой другой язык.

1 голос
/ 11 марта 2011

Обычным алгоритмом тасования и одним из немногих непредвзятых является тасование Фишера-Йейтса . Каждая итерация алгоритма принимает случайное число и заменяет два места на основе этого числа. Сохраняя список этих случайных чисел, вы можете впоследствии воспроизвести точно такую ​​же перестановку.

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

0 голосов
/ 11 марта 2011

Прошу прощения, если об этом уже говорилось в предыдущем ответе ,, но впервые ,, эти ответы совершенно чужды мне.Я мог бы упомянуть, что знаю Java и JavaScript и ничего не знаю о математике ... Так что log2, permutations, factorial, well-ordering - всеНеизвестные слова для меня.

И к тому же я снова (снова) использовал StackOverflow в качестве белой доски, чтобы выписать свой вопрос и ответил на вопрос в моей голове 20 минут спустя.Я был привязан к некомпьютерной жизни и, зная StackOverflow, я подумал, что уже слишком поздно экономить более 20% легко теряемого времени каждого.

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

(написан на Javascript, но должно быть легко перевести 20 строк иностранного кода на выбранный вами язык)

(см. Его в действии здесь: http://jsfiddle.net/M3vHC)

Редактировать: Спасибо AShelly за этот улов: Это не удастся (стать предвзятым), если длина ключа больше 12, если ваши целые числа равны 32бит (больше 19, если ваши целые 64-битные)

var keyLength = 5
var possibilities = 1
for(var i = 0; i < keyLength ; i++)
    possibilities *= i+1 // Calculate the number of possibilities to create an unbiased key
var randomKey = parseInt(Math.random()*possibilities) // Your shuffle instruction. Random number with correct number of possibilities starting with zero as the first possibility
var keyArray = new Array(keyLength) // This will contain the new locations of existing indexes. [0,1,2,3,4] means no shuffle [4,3,2,1,0] means reverse order. etcetera
var remainsOfKey = randomKey // Our "working" key. This is disposible / single use.
var taken = new Array(keyLength) // Tells if an index has already been accounted for in the keyArray
for(var i = keyArray.length;i > 0;i--) { // The number of possibilities for the first item in the key array is the number of blanks in key array.
    var add = remainsOfKey % i + 1, remainsOfKey = parseInt(randomKey / i) // Grab a number at least zero and less then the number of blanks in the keyArray
    for(var j = 0; add; j++) // If we got x from the above line, make sure x is not already taken
        if(!taken[j])
            add--
    taken[keyArray[i-1] = --j] = true // Take what we have because it is right
}
alert('Based on a key length of ' + keyLength + ' and a random key of ' + randomKey + ' the new indexes are ... ' + keyArray.join(',') + ' !')
...