Поки що не можу остаточно розтлумачити приклад life, який я наводив вище, але можу написати свій аналог, що видає такий же результат:
mylife←{((⍵×9)+⊃+/+/¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵)∊12 13 3}
Спробую пояснити, як я це зробив.
Перше, що треба знати про APL: усі оператори (які можуть унарними чи бінарними) мають однаковий пріоритет і є правоасоціативними — тому 2×2+2 = 2×(2+2) = 8, а 1-2-3 = 1-(2-3) = 1-¯1 = 2. Це пояснить, чому в одних випадках дужки потрібні, а в інших без них можна обійтися. Друге: всі дані бувають двох основних типів: скаляри і масиви. Одновимірні масиви чисел задаються серією чисел, розділених, пробілами. Дво- та багатовимірні можна зробити з одновимірних оператором ⍴, напр.:
2 3⍴1 2 3 4 5 6
1 2 3
4 5 6
У грі ми будемо задавати ігрове поле двовимірним масивом нулів та одиниць:
map←4 4⍴0 0 0 0 1 1 1 0 0 0 0 0
0 0 0 0
1 1 1 0
0 0 0 0
0 0 0 0
(якщо праворуч від ⍴ менше чисел, ніж треба для заданої розмірності, він переходить до початку вхідного масиву і йде по новому кругу, тому все ОК).
Щоб знайти положення наступного ходу, нам треба порахувати кількість одиниць у сусідніх клітинках. За правилами, заповнена клітинка, біля якої є 2 або 3 сусіди, лишається заповненою, якщо більше або менше — звільняється; якщо біля порожньої клітинки є рівно 3 заповнені, вона також стає заповненою. Отже, нам треба знайти суму всіх сусідніх клітинок з особливим урахуванням поточної.
В APL є оператори прокрутки ⌽ та ⊖:
1⌽map
0 0 0 0
1 1 0 1
0 0 0 0
0 0 0 0
¯1⌽map
0 0 0 0
0 1 1 1
0 0 0 0
0 0 0 0
1⊖map
1 1 1 0
0 0 0 0
0 0 0 0
0 0 0 0
¯1⊖map
0 0 0 0
0 0 0 0
1 1 1 0
0 0 0 0
Нам треба зробити всі можливі комбінації зсувів на -1, 0, 1 по горизонталі та вертикалі. Проблема в тому, що для адекватної роботи операторів прокрутки ліворуч має бути число, а не масив. Можна застосувати модифікатор ∘. — але й він робить незовсім те, що нам треба.
¯1 0 1∘.⌽map
0 0 0 0
1 1 1 0
0 0 0 0
0 0 0 0
0 0 0 0
1 1 1 0
0 0 0 0
0 0 0 0
0 0 0 0
1 1 1 0
0 0 0 0
0 0 0 0
Що відбулося: правий і лівий масив було розбито на окремі елементи, до кожної їх комбінації було застосовано прокрутку (що для числа не дає ніякого ефекту), результат зібрано в тривимірний масив, котрий, як бачимо, являє собою три дублікати початкового масиву.
Щоб обійти цю проблему, можна загорнути масив у скалярний контейнер оператором ⊂
¯1 0 1∘.⌽⊂map
0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0 1 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
Працює!
Контейнер є для зовнішнього світу скаляром, тому модифікатор комбінаційного добутку не розбирає його на елементи, всередині ж він лишається масивом і при обчисленні результату обробляється як масив. При виводі масив у контейнері та без виглядають ідентично, але їх можна відрізнити за розмірністю (унарний оператор ⍴):
⍴map
4 4
⍴⊂map
(пустий рядок)
Зробімо тепер усі можливі комбінації прокрутки:
{¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}map
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0 1 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0 1 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0 1 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
Ми отримали двовимірний масив контейнерів двовимірних масивів. Ця частина коду присутня в обох реалізаціях — моїй і з вікіпедії. Далі треба зробити з них один масив із сумами сусідніх клітинок:
{+/+/¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}map
2 3 2 2
2 3 2 2
2 3 2 2
0 0 0 0
Оператор + виконує додавання, а / поруч із ним означає не ділення (для якого використовується ÷), а редукцію — тобто, +/1 2 3 4 означатиме те ж саме, що 1+2+3+4. Арифметичні операції вміють працювати з двома числами, числом і масивом, двома масивами однакової розмірності. У даному випадку, між собою додаються скалярні контейнери з двовимірними масивами однакової розмірності, а результатом є скалярний контейнер, де кожен елемент являє собою суму елементів вхідних масивів у відповідних позиціях. Редукція відбувається лише в одному вимірі, тому її треба застосувати двічі. В результаті, ми отримуємо контейнер з масивом, кожен елемент якого містить суму сусідніх клітинок (включаючи поточну) початкового ігрового поля.
Після цього масив виймається з контейнера оператором ⊃, до нього додається початковий масив, кожен елемент якого помножено на 9:
{(9×⍵)+⊃+/+/¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}map
2 3 2 2
11 12 11 2
2 3 2 2
0 0 0 0
Таким чином, порожня клітинка з 0, 1,…, 8 сусідів дасть результат 0, 1,…, 8, а заповнена з 0, 1,…, 8 сусідів — 10, 11,…, 18 відповідно. Далі нам треба лише вписати одинички в порожні клітинки з трьома сусідами й заповнені з двома чи трьома сусідами, а решту заповнити нулями (оператор ∊ — аналог pythonівського in, подібний символ використовується в схожій ролі в математичних формулах):
{((9×⍵)+⊃+/+/¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵)∊3 12 13}map
0 1 0 0
0 1 0 0
0 1 0 0
0 0 0 0
Результат відповідає очікуваному. Можна оформити цей вираз як функцію й користуватись нею:
mylife←{((9×⍵)+⊃+/+/¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵)∊3 12 13}
#procedure
mylife map
0 1 0 0
0 1 0 0
0 1 0 0
0 0 0 0
mylife mylife map
0 0 0 0
1 1 1 0
0 0 0 0
0 0 0 0