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

в 21:21, , рубрики: ненормальное программирование, тацитное программирование, функциональное программирование, метки:

Предыдущая статья цикла Язык программирования 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js