Pull to refresh

Язык программирования J. Взгляд любителя. Часть 3. Массивы

Reading time5 min
Views7.8K
Предыдущая статья цикла Язык программирования 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-го элемента каждой строки левого операнда.

Заключительная статья цикла Язык программирования J. Взгляд любителя. Часть 4. Коробки и циклы. Заключение
Tags:
Hubs:
Total votes 22: ↑21 and ↓1+20
Comments1

Articles