Perfect shuffle

59f0a58e22ba0685844423.png

Меня всегда привлекали элементарные алгоритмы, с помощью которых можно создавать сложные паттерны. Есть в таких алгоритмах что-то фундаментальное. Один из таких алгоритмов — Perfect Shuffle. Посмотрим на его необычные свойства, а также попробуем нарисовать несколько впечатляющих фракталов с помощью этого алгоритма.

Дальше много картинок, gif-анимации и немного музыки.

Perfect Shuffle известен в среде фокусников-картежников. Называют они его Faro Shuffle. Недавно тоже захотел научиться показывать карточные фокусы. С чего начинаются фокусы? С развития ловкости рук. Повертев неделю колоду карт, освоил основные способы тасовки — «Riffle Shuffle», «Faro Shuffle», «Charlier Pass». Тренируясь делать Faro Shuffle, однажды упорядочил колоду так, чтобы все черные масти находились в начале колоды, а красные — в конце. Сделав несколько раз Faro Shuffle, обратил внимание на необычный порядок карт в колоде. Карты отложил и сел программировать.

Данный алгоритм меняет порядок элементов в массиве (перемешивает массив). Сей алгоритм элементарен:
1. Делим массив на две равные части.
2. Элементы из первой половины массива размещаем на четных позициях. Из второй — на нечетных.

Наглядно:

59f0c258689be693972011.png

Еще наглядней:

JavaScript:
function shuffle(array){
	var half=array.length/2; //здесь мы смело делим длину массива пополам
	var temparray=[];
	for(var i=0;i


Небольшое лирическое отступление
Такой вариант Perfect Shuffle картежники называют «in-shuffle». Есть и другой вариант («out-shuffle») — элементы из первой половины массива размещаем на нечетных позициях:

59f0c6f0c9e5a145424353.png

Как видим, крайние элементы остаются на своих позициях, а середина массива перемешивается тем же in-shuffle. Вариант out-shuffle далее не рассматриваем.

Кроме того, не рассматриваем вариант, когда количество элементов в массиве нечетное — последний элемент в массиве остается на своей позиции.


У алгоритма есть несколько примечательных свойств. Если перемешать массив несколько раз — через n итераций все элементы вернутся в исходную позицию! Это очевидно. Алгоритм детерминированный. Для каждого следующего состояния массива существует только одно уникальное предыдущее состояние. Количество всех возможных состояний ограничено длиной массива. Если мы будем перемешивать массив итеративно — рано или поздно мы исчерпаем возможные состояния и пойдем по второму кругу. На самом деле, это случится гораздо раньше. Количество итераций, необходимых для возврата массива в исходное состояние — меньше (или равно) количества элементов в массиве. «Меньше или равно» — все, что можно сказать о количестве итераций.

Для массива из 12-ти элементов хватит 12-ти итераций:

59f0ce4d7da57797540724.png

Для массива из 14-ти элементов хватит всего 4 итерации:

59f0cf10e3b2b972779140.png

На картинке ниже (кликабельно) перемешиваем массивы с длинами от 2-х элементов до 20-ти:

59f0cf7b94d08581254571.png
(unshuffle)

Перечисленные массивы возвращаются в исходное состояние на
2, 4, 3, 6, 10, 12, 4, 8, 18, 6
итерациях. Последовательность A002326

JavaScript
function a002326(n){
	var a=1;
	var m=0;
	do{
		a*=2;
		a%=2*n+1;
		m++;		
	}while(a>1);
	return m;
}
for(var n=1;n<11;n++){
	m=a002326(n);
	console.log(m);
}

Построим график! На графике по оси X отметим количество элементов в массиве (деленное на 2). По оси Y — количество итераций. Белыми точками обозначим исходные состояния массивов (начало координат — левый верхний угол):

59f0d9103a7c0197965270.png

Согласитесь, точки на графике размещаются хаотично.

Еще одно необычное свойство Perfect Shuffle — после некоторого количества итераций, порядок элементов в массиве может измениться на обратный. Выше на картинке мы перемешивали массив из 12-ти элементов. Порядок элементов меняется на обратный на 6-й итерации. Нарисуем еще один график, на котором отметим массивы с обратным порядком элементов:

59f0d91e71a5b042472770.png

Опять же, размещение точек, не менее хаотично.

Очень интересно понаблюдать за перемещением элементов в массиве! Как элементов из первой половины массива в совокупности, так и конкретного (первого) элемента в частности.

Заполним первую половину массива нулями, вторую — единицами (далее на картинках нули — черные пиксели, единицы — белые). На картинках ниже, каждая строка (X) — состояние массива. По Y — итерации.

Несколько массивов (142–150 элементов):

59f0e173e6e1a494534941.png 59f0e173c464f688553413.png 59f0e174184c2603824511.png 59f0e1742b63f526546037.png 59f0e1744be7d263918027.png
Картинки кликабельны

288 элементов (144×2):

pma152yulqsic1o1yh5utxasy1w.png

610 элементов:

59f0e2bc835d5519613640.png

Для других длин массива
Небольшой скрипт, который рисует такие замечательные картинки. Вбиваем размер массива (половины массива), жмем «Draw».


К слову
Об одном замечательном способе визуализации бинарных последовательностей рассказал в первой своей статье
Массив из 142-х элементов, на 55-й итерации выглядит вот так:
0011001100011001100011001100011001100111001100111001100111001100111001100110001100110001100110001100110001100110011100110011100110011100110011
Эту последовательность можем вбить сюда и посмотреть на этот массив более наглядно:

59f0f6880c577205936187.png

А клеточные автоматы как замечательно смотрятся!
59f0f7ccd69fa378697603.gif59f0f75b3de36134900032.gif
59f0f7dd7375e097917897.gif59f0f7ea68276921388521.gif

59f0f79c62c54484148352.gif


Как видим, и здесь у нас сплошной хаос. Понаблюдаем за отдельным элементом и его траекторией в массиве.

Массив 610 элементов. Заполнен только первый элемент:

qv2-_ocjwjhxsd9ycdpdi4px7us.png

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

59f0ec4c976a8443465742.png
(Y — длина массива)

Первые массивы (с длинами от 2-х до 22-х):

59f0ecd767eef711696414.png

На картинке выше: в верхней части картинки массив и положение элемента на каждой итерации, в нижней части линии — все возможные положения элемента. Как видим, траектория элемента внутри массива тоже не очевидна. Чтобы окончательно в этом убедиться — из этих линий сделаем еще один график, сложив их «стопочкой»:

59f0eea06b61f948107810.png
Кликабельно

Черные пиксели — те позиции в массиве, в которые не попадает первый элемент при перемешивании.

На этом можно было бы остановиться, но мы пойдем еще дальше! Понаблюдаем за перемещением всех элементов в массиве. Для этого перейдем к матрицам.

Но прежде, чем переходить к перемешиванию матриц, покажу еще два фокуса с массивами.

Во второй половине массива меняем порядок элементов на обратный (на каждой итерации):

shv4rv9ndqahddzzsfa5nvoilfo.png

Во второй половине инвертируем элементы:

ttaqbpjhamr8b8hounz8bopsou0.png

Две эти операции нам еще пригодятся.

Перейдем к матрицам!

Заполним в матрице, в первой строке — первый элемент, во второй — второй и т.д. Другими словами, нарисуем диагональ:

abdpqvrpxkmq2vhznxfxhr4ybbc.png

Перемешаем с помощью Perfect Shuffle столбики в матрице. Так мы сможем посмотреть, куда попадают все элементы после перемешивания. Матрица 80×80:

jmxl_dhks_bpv9zoe_xqfy8thc4.png

Тут явно не хватает еще одной диагонали:

0k2xvmwoscfbkydtr6lhbce42j0.png

Очень любопытный муар получается. Матрица 242×242 с 27 по 35 итерацию:

uniqauubhq29j5fswfzzaknchr4.png

Ну и если уже начали перемешивать столбцы в матрице с помощью Perfect Shuffle — почему бы не перемешать и строки?

Немного модифицируем нашу функцию:

Функция, перемешивающая строки и столбцы в матрице
function shufflecr(array){
	var half=array.length/2;
	var temparray=[];
	
	for(var x=0;x

Делим матрицу на 4 части. Перемешиваем столбцы. Перемешиваем строки. Получаем новую матрицу. Наглядно:

ppbhyxplwevxxxwt-su0huevam0.png

Диагонали перемешиваются довольно скучно:

zesujlcvnudroby5epr489gvqic.png

Может даже показаться, что в функции где-то ошибка и матрица не перемешивается. Сдвинем диагонали на один пиксель вправо:

rrusoebgkrccvbohxipj93_1oqa.png

Замечательно. Функция работает, матрица перемешивается. Правда опять же довольно скучно. Попробуем заполнить матрицу каким-то паттерном. В рандомном месте нарисуем квадрат:

ckm9ntdv8rpbcyhsbvry9h2omxs.png
Размер матрицы 80×80 выбрал не случайно. При перемешивании массива длиной 80 элементов, на 27-й итерации порядок элементов меняется на обратный.

Подвигаем этот квадрат!

2om_etw_lvse2qiuonuiq4dsl6o.gif

Тут можно заметить одно очень интересное свойство, но еще лучше это свойство видно, если нарисовать окружность:

nhnblrexjqxj9xtjbmvbfmzvcfq.png

Обратите внимание на краевые эффекты. Наша матрица сворачивается в тор! Это хорошо видно на второй итерации.

Подвигаем:

xtfsdtyrmcpmq3tnels51ns6tgs.gif

К слову
У Perfect Shuffle есть любопытная связь с тригонометрией.

Выше мы перемешивали массив и получили несколько замечательных картинок. Одна из них (144×2):

pma152yulqsic1o1yh5utxasy1w.png

Ее тоже запишем в матрицу и перемешаем:

aqetvkqhofx_ixqcb5zbpjvldl4.png

Или даже так. Возьмем исходный паттерн:

pma152yulqsic1o1yh5utxasy1w.png

И оставим на нем пиксели с координатами x*4+i, y*4+j. Для i=2, j=2:

5miqbjm1amxkl3sjfovlms3zvi4.png

Уберем пустые столбцы и строки:

h_v3fg3ezvdqkydyjb3lb5gpkha.png

Для других i и j (фактически сделали perfect unshuffle — разделили матрицу на 16 частей):

5lwzxhzjd_1m3b4liemosbpdtpw.png

Знакомые паттерны! Похожие паттерны можно нарисовать с помощью тригонометрических функций z=sin (n*x/y) и z=sin (n*x*y)

График функции z=sin (3*x*y). Если z>=0 — белый пиксель. Если z<0 — черный:

zwqmy0qfm2pifvyjomy30ui4lnq.png

z=sin (13*x*y):

nj9_dafjz4qyrcgjrdaf_d97rza.png


Один из интересных паттернов:

skidegavrihwhif97yiwujyuwco.png

Можем сделать заливку, чтобы выделить замкнутые области:

44qiaq8ds5ntnd-89b1nohil2sa.png

К слову
Старый, добрый и всеми любимый ослик (Internet Explorer) не использует хитрых алгоритмов уменьшения изображений. Он просто берет и выбрасывает из картинки каждый n-й столбик и n-ю строку пикселей. Если их не выбрасывать, а складывать в отдельные матрицы — получим процесс (unshuffle) обратный Perfect Shuffle (при n=2).
На самом деле это довольно круто! Можем взять такой шаблон:

90c9agzzn3i8vy3luiirmfxj7o0.png

И немного сжать его осликом:

77odiue49osnkynohspek5jmnfe.png

Получили знакомый паттерн.

Можно поиграться с другими шаблонами:

pb0xycdd9g8om8gxxohcol7ib8a.png

Сжали осликом:

xpnk2rr3qdxclhlpmk-c4icdyz0.png

Вспомним о фокусах (изменение порядка элементов на обратный и инвертирование).

Изменение порядка элементов дает очень интересные паттерны! Один из них:

cqv_jab4-xggakyb6lcwqwwuuak.png

С заливкой:

lfurlk105yxr7zywdajxw54zcaq.png

Другие паттерны не менее интересны, но на них останавливаться не будем. Гораздо интересней понаблюдать за инвертированием.

Для этого эксперимента нам хватит пустой матрицы. Разделим ее на 4 части. Две из них инвертируем. Перемешаем. На первой итерации ничего необычного:

0qcphvhl8rhamsnc-gzi7pprj1e.png

А вот дальше появляются любопытные паттерны:

kdssk8zj6atdn2jq04tkwt7yloc.png

Чтобы разобраться, откуда берутся эти паттерны, вернемся еще раз к массивам. До этого мы использовали матрицы и массивы фиксированной длины. Попробуем немного изменить подход — длину будем увеличивать постепенно:
1. Создадим массив из n элементов.
2. Сделаем его копию.
3. Перемешаем копию с оригиналом с помощью Perfect Shuffle. Получили массив из n*2 элементов.
4. Вернемся к шагу 2.
Копию каждый раз будем инвертировать.

Начнем с массива из двух элементов — 1 и 0
10 — исходный массив
10, 01 — массив и инвертированная копия
0110 — смешали
0110, 1001 — массив и инвертированная копия
10010110
10010110, 01101001
0110100110010110
… и т.д.
Получили последовательность Морса — Туэ (A010060) — простейший фрактал!

И обратно к матрицам. Вот тут начинается чистое искусство. До этого мы делили матрицу на 4 части и перемешивали эти части. Попробуем копировать матрицу, производить над копиями некоторые элементарные действия и перемешивать оригинал с копиями.

С матрицами можем произвести 15 элементарных действий. Действие 0 — матрица без изменений. Действия 1–7 — разные способы вращения матрицы. Действия 8–15 — инвертирование. Наглядно:

rfgjq2sw8ebx_nb0ikvavc1-ip8.png

В качестве примера, произведем над копиями действия 0, 0, 7, 7. Действие 7 — поворот матрицы на 90°.

yb3vthmiu2szo9a2tkxahhlbn7u.png

Следующая итерация:

dnwd9guwkansr8b0jo6-6mzklg8.png

… восьмая итерация:

nmp75bmugmw8exgocddxt6o1pd0.png

Очень необычный фрактал получился.

К слову
Аналогичный способ рисовать фракталы придумал еще в студенческие годы. Выполняем те же действия, только не перемешивая матрицы с помощью Perfect Shuffle.

Действия 0, 0, 7, 7. Первая итерация:

vfxuicqpiye2pc0z3yf6iqnbowm.png

Вторая итерация:

rku6n3oxtwupj1nyko8zuic6cms.png

Восьмая:

zqil_bvfuphntjojvcz9bpgmz4o.png

А теперь два фокуса.

Накладываем сверху копию картинки с прозрачными белыми пикселями, копию поворачиваем на 90°:

0hdyadhdvzmyz2dijljaso-5gbm.png

Копия повернута по вертикали:

zuurf-4wu1jq57yp4pbu1eoejki.png

Комбинируя перечисленные действия, можно нарисовать 16^4=65536 фракталов. Некоторые из них:

Нарисованы за 6 итераций. Картинки кликабельны (8 итераций).
452g_z0phqdequkra4cqzuh3o5c.pngrsls0ixru2tldujzgwszgc6yy98.pngwuktfzwq9822m005qlwxe1si2za.pngucuccndnvjxua52nbzu9jkgofqm.pngrotabxrfv8hcsmarbb-eesxjo-s.png
suewsy-h6flnzbs37lixsjzldvc.pngf0edfqys01iswv2h6ehqtuhzm4s.png7jsdawzc-824g-wskief5kt76cw.pngeoqyidd8clkdshf3cbmpunh-xdu.pnglmzp6xy_rhzah8zzufaokaozexo.png
f1w67bje8_b0j1gqnxzqhixwesk.pngf5oeuixf2icezstjb6ultje9ii0.pngd1rluvog-0a7oglfr3kchvnohm0.pngjf_wgxjgx2bf270pm_wyw69jfza.pnge6pvpjrzwrjtkcwiz3m3yqh5ris.png
ywgubre3eeoxl34_o2d4p8_bk1a.pngroosnuu2oowjunsaj81pvj8f2hm.pngwtzv-deqsexmwuu3_mws_fkizhy.pngxdcppcwnki4m1dmfmdn-upwr2ae.png85cbc449h_xdszy7bbahczg22ci.png
4hpumnz7v7hkyryoq-etxgpl1lk.pngdgd8yvxfvvty7zvdrp1zr61gwqw.pngpdhlt-9icdflnwmpxkxz38_1uba.pngrs_eux9ydpyp8vzcijstl4lei64.png4cp2qj8a2mcsejd68zv94wu96oy.png
s8uriexfllt6b0a5x9o17nqgl7g.pngg6w3ovaa8g1zudlpn-voaazyeay.pngaxk-9hlvan8yk650eij7q9cpomo.pngxwldcoqo0svunpflwusv-vz-6_8.png71jc_svyynnjhwc96ha4167rx4g.png
5qpkfjgnvcwmzx-wlpzzfwvm7w0.pngww5ff7haxfuxmmrvytc9ilbbo_m.png

А вот эти тянут на шедевр:

15, 5, 9, 5:
qfk9j1tiqgyijnktucujk1w7-kg.png

3, 5, 9, 5:
x77ynkviekxi1ukvp18jtbbj7li.png

13, 7, 11, 11:
trm4pnv7ibwj9qqsqb1mx9vwt-c.png

0, 0, 7, 13:
0-s3qm0qbepfgguh7tu5g-spumg.png

1, 1, 4, 14:
txgdeqkttxbfx0jjdz0xkmaq6ty.png

Другие фракталы можно нарисовать здесь

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

Для тех, кто дочитал до этого места
Пробовал написать генератор мелодий на основе генетического алгоритма. Каждая мелодия состоит из нот-генов. Если гены потомков смешивать рандомно — получается какофония. А вот если смешивать их с помощью Perfect Shuffle — получаются любопытные мелодии. Одна из них:


К слову
ne2l8bj3pvdxnnizsknt_y0_0dk.png

r9mzpnfraq7vcnmqmbmgjeosufu.jpeg

bzqomwmsaepoix293assixe_4tm.jpeg

© Habrahabr.ru