Предыдущая статья цикла Язык программирования J. Взгляд любителя. Часть 2. Тацитное программирование
«Я не думаю, что он нам подходит. Я рассказал ему, чем мы занимаемся, и он не стал спорить. Он просто слушал.»
Кен Айверсон после одного из собеседований
1. Массивы
J – язык для обработки массивов. Для создания массивов в J есть множество способов. Например:
- «$» — этот глагол возвращает массив, размерность которого указывается в левом операнде, а содержимое — в правом. Создадим массив заданной размерности, все элементы которого одинаковы:
3 $ 1 NB. создаем вектор с тремя элементами, каждый из которых = 1 1 1 1 2 3 $ 2 NB. создаем матрицу из 2 строк и 3 столбцов, все элементы которой = 2 2 2 2 2 2 2
Можно также правым операндом указывать произвольный вектор, элементы которого будут последовательно копироваться в результирующий массив:2 3 $ 1 2 3 1 2 3 1 2 3 2 3 $ 1 2 1 2 1 2 1 2 2 3 $ 1 2 3 4 1 2 3 4 1 2
- «#» — в диадном варианте глагол копирования. Копирует i-й элемент правого операнда столько раз, сколько указано в i-м элементе левого операнда. Таким образом, длина результирующего массива равна сумме элементов x. Пример:
1 2 0 3 # 1 2 3 4 1 2 2 4 4 4 4 # 1 1 1 1 1
- «i.» создает перечисления и таблицы. В монадном вызове возвращает массив, составленный из целых чисел (начиная с нуля), каждое из которых больше предыдущего на единицу. Длина такого массива задается правым операндом. Если значение операнда отрицательно, то числа в результирующем массиве идут в обратном порядке:
i.4 NB. пробел между глаголом и операндом необязателен 0 1 2 3 i._4 3 2 1 0 i.2 3 0 1 2 3 4 5
Обратите внимание на последний пример — глагол «i.»возвратил нам двумерный массив, т.к. переданный ему операнд был вектором. Первый элемент операнда указывает на число строк, второй — столбцов. Впрочем, с помощью этого глагола можно получить и n-мерный массив. Например, трехмерный:
i.2 _2 3
3 4 5
0 1 2
9 10 11
6 7 8
Строки в результирующем массиве идут в обратном порядке, т.к. вторая размерность задана отрицательным числом. Подробней о многомерных массивах мы поговорим в следующем разделе.
В связи с тем, что многие стандартные глаголы J способны обрабатывать массивы данных, можно расширить описанные ранее глаголы на случай массивов.
- 1 2 3
_1 _2 _3
Диадный случай:
1 2 3 - 3 2 1
_2 0 2
Кроме того, допустимы и операции над разноранговыми значениями:
1 2 3 - 1
0 1 2
Тот же результат можно получить и с помощью глагола <:
<: 1 2 3
0 1 2
Допустима и обратная операция:
1 - 1 2 3
0 _1 _2
Напомним, что последнее выражение можно записать и как «1 2 3 -~ 1».
Кроме стандартных арифметических глаголов в нашей работе пригодится глагол-генератор псевдослучайных чисел «?». Будучи вызванным с одним операндом «?» возвращает:
- случайное число с плавающей точкой, если операнд равен нулю;
- случайное целое число в промежутке от нуля до y, если операнд равен y.
? 0 NB. конечно, у вас результат будет отличаться
0.622471
? 3 NB. вернет случайное число на отрезке [0;2]
2
? 3
0
Установить датчик псевдослучайных чисел можно с помощью вызова
9!:1 y
Начальное значение seed = 7^5.
Как и во всех примерах ранее в этом разделе, глагол «?» можно вызывать с операндом-массивом — результатом будет массив той же размерности, каждым i-м элементом которого будет случайное число на интервале, заданным i-м элементом операнда:
? 0 10 100
0.429769 7 95
Глагол «?», разумеется, работает не только с векторами, но и, например, с матрицами:
? (2 2 $ 0 10)
0.084712 4
0.840877 1
2. Части речи для работы с массивами
Как мы уже знаем, J — язык для обработки массивов. Выражается это, не в последнюю очередь, в том, что при работе с массивами вам практически не придется пользоваться явными итерационными процедурами.
Предположим, вам необходимо найти сумму всех элементов последовательности на подмножестве языка Python. Обобщим эту задачу до стандартной функции свертки:
def reduce(xs, f, acc = 0):
"""Пример запуска:
>>> reduce([1,2,3], lambda acc,x: acc + x)
6"""
for x in xs:
acc = f(acc,x)
return acc
Для решения таких задач в J есть специальное наречие «/», называемое «между». Действительно, наша функция на питоне эквивалентна следующему выражению «x0 f x1 f … f xN». В терминах J это записывается как «f/ xs», где xs — существительное(вектор), f — глагол, который вставляется «между» элементами существительного xs, «/» — наречие, которое, собственно и осуществляет такое преобразование. Приведем пример:
+/ 1 2 3 NB. аналогично «1 + 2 + 3»
6
-/ i.3 NB. аналогично 0 - (1 - 2)
1
А что, если нам надо возвратить в результате свертки не только конечный результат вычислений, но и все промежуточные результаты (в контексте исходного кода на Python — все промежуточные значения переменной «acc»)? Т.е., например, для вектора «1 2 3 4» после применения глагола «+»«между» ожидается получить «1 3 6 10».
В J для этой цели есть специальное наречие «»:
+/ 1 2 3 4 NB. эквивалентно выражению: (1) , (1+2), (1+2+3), (1+2+3+4)
1 3 6 10
-/ 0 1 2 NB. эквивалентно выражению: (0), (0-1), (0-(1-2))
0 _1 1
Другие необходимые глаголы — это «/:» и «:», которые сортируют переданный вектор по возрастанию и по убыванию соответственно. Причем результатом сортировки является вектор из индексов отсортированных элементов:
/: 1 3 2
0 2 1
: 1 3 2
1 2 0
Для того, чтобы получить по указанным индексам элементы массива воспользуемся глаголом «{», который извлекает элементы из массива (правый операнд) по указанным индексам (левый операнд). Например:
1 0 1 2 { 11 22 33 44
22 11 22 33
Другими глаголами для «ручного» индексирования элементов массива являются
- «}.» возвращает «хвост»массива, т.е. все элементы кроме первого.
- «{.» возвращает «голову»массива, т.е. первый элемент массива.
- «{:» возвращает последний элемент массива.
- «}:» возвращает все элементы массива кроме последнего.
Вспомним наречие «~», которое меняет в вызове правый и левый операнд местами, и приведем несколько более сложный пример:
({~ /:) (? (5 $ 0))
0.221507 0.293786 0.691701 0.72826 0.839186
В данном примере генерируется последовательность из 5 случайных вещественных чисел, затем к результату применяется хук из глаголов «{~» и «/:».
3. Многомерные массивы и ранги
Про многомерные массивы мы уже упоминали. Логично было бы предположить, что раз стандартные глаголы работают как с числами, так и с векторами чисел, то так же точно они могут обрабатывать многомерные массивы данных.
]x =: i.2 3 NB. глагол ] возвращает свой правый операнд, т.е. в данном случае переменную x
0 1 2
3 4 5
x + 10 20 30
|length error
x + 10 20
10 11 12
23 24 25
Как мы видим, если выполнить стандартный глагол над матрицей и вектором, то по умолчанию будет выполняться действие «по столбцам». В примере «10» прибавляется к элементу на первой строке каждого столбца, а «20» — на второй строке. Размерность массива в примере 2 на 3. Будем говорить тогда, что первый ранг этого массива равен 2, а второй ранг равен 3. Раз по умолчанию глагол применяется к столбцам матрицы, то можно сказать, что он применяется по второму рангу.
Это общее правило для J — глаголы по умолчанию применяются к крайнему рангу массива. Для того, чтобы явно указать ранг глагола используется специальный союз «"» (двойные кавычки), который левым операндом принимает глагол, правым — ранг (целое число). Например:
(i.2 3) + 10 20
10 11 12
23 24 25
(i.2 3) +"2 (10 20)
10 11 12
23 24 25
Как видим, эти два выражения эквивалентны. Обратите внимание на скобки вокруг вектора (10 20). Если их не поставить, то транслятор J будет считать, что ранг глагола равен «2 10 20», а правый операнд у глагола не указан. Чтобы явно не указывать ранг глагола, рекомендуется использовать знак бесконечности «_»:
(i.2 3) +"_ (10 20)
10 11 12
23 24 25
Результат от этого не изменится. Если же поменять ранг глагола на 1:
(i.2 3) +"1 (10 20)
|length error
Ошибка данного выражения заключается в том, что мы пытаемся применить глагол суммирования к вектору длиной 2 (правый операнд) и к вектору-строке левого операнда длиной 3. Изменим немного наш пример:
(i.2 3) +"1 (10 20 30)
10 21 32
13 24 35
Результат соответствует последовательному суммированию i-го элемента правого операнда и каждого i-го элемента каждой строки левого операнда.
Продолжение следует...
Автор: basp