>>933> Анонимус, можешь прокомментировать как это работает?
Могу, конечно.
Чтобы было понятнее, я напишу программу из восьми отдельных строк (потом объединю их в одну). Это будет несколько другое решение, чем приведенное ранее, поскольку первый вариант ограничен случаем трех автобусов.
Выясним сначала, сколько человек должно оказаться в каждом автобусе после пересадки:
mean =: +/ % #
=: обозначает присваивание: в данном случае мы присваиваем имени mean функцию вычисления среднего (или функции присваиваем имя). Аргументом функции mean будет массив натуральных чисел (начальное количество пассажиров в каждом автобусе). Среднее - это "сумма всех элментов делить на их количество", верно? Так и пишем: сумма (+/) делить (%) на количество (#). # x возвращает количество элементов в x, x % y делит x на y, а на +/ стоит остановиться подробнее. Сам по себе +, понятное дело, вычисляет сумму. А вот / делает из + новую функцию, которая получает в качестве аргумента массив y и вычисляет y[0] + y[1] + y[2] + ... Точно так же можно написать */ и получить функцию, которая считает произведение всех элементов массива.
Вся конструкция из трех функий называется "вилкой". Когда J видит что-то вроде
(f g h) y
, он интерпретерует это как
(f y) g (h y)
. Или, если аргументов два (слева и справа), то
x (f g h) y
интерпретируется как
(x f y) g (x h y)
. Просто, понятно, легко запоминить: например,
- * +
это "разность, умноженная на сумму". Как слышится, так и пишется.
Если теперь мы напишем
mean 25 21 14
, оно ответит нам "20".
Теперь посчитаем, сколько человек должно выйти из каждого автобуса.
getout =: ] - mean
] - это функция, просто возвращающая свой правый аргумент. - это минус. Вся конструкция - уже знакомая нам вилка, и означает она "из массива (]) вычесть (-) среднее значение этого массива (mean)". Вычитание одного числа из массива - это вычитание этого числа из каждого элмента массива (что логично). Результатом вычисления
getout 25 21 14
будет массив 5 1 _6. (знаком подчеркивания обозначаются отрицательные значения).
Из первого автобуса должны выйти 5 человек, из второго 1, из последнего -6 (то есть в него должно сесть 6 человек). Для того, чтобы найти ответ на поставленный в задаче вопрос, надо найти общее количество выходящих - то есть надо сложить все положительные числа из полученного массива. Напишем вот такую функцию:
positive =: >&0
Функция > возвращает 1, если ее первый аргумент больше второго. С помощью & мы делаем из функции сравнения функцию сравнения с нулем (точно так же мы могли бы получить функцию умножения на три, написав *&3). Если применить эту функцию к массиву 5 1 _6, то мы получим 1 1 0. Теперь, когда мы научились отличать положительные числа, нам не составит труда их просуммировать:
sumofpositive =: +/ @ (] * positive)
В скобочках уже знакомая вилка из знакомых функций: здесь сам массив умножается на массив нулей и единиц, вычисленный функцией positive. Умножением массивов делается поэлементно, так что умножив 5 1 _6 на 1 1 0 мы получим 5 1 0 - массив, в котором остались только положительные значения. +/ вычисляет сумму элементов этого массива. @ обозначает композицию двух фукнций, то есть указывает на то, что суммировать надо результат вычисления в скобках. Результат вычисления
sumofpositive 5 1 _6
равен 6. Задача почти решена - осталось только рассмотреть случай, когда пересадка невозможна. А она будет невозможна только в одном случае - если из какого-то автобуса должно выйти дробное количество пассажиров. Напишем функцию проверки на натуральность:
integer =: <. = ]
<. - это округление вниз. = - это равно. Все вместе читается "округленное значение числа равно самому этому числу". Если вычислить
integer 10 20 0.5
мы получим 1 1 0. Теперь напишем еще одну функцию:
allintegers =: *./ @ integer
*. - это логическое "И". А / делает из этого "И" функцию, которая будет вычислить y[0] И y[1] И y[2] ... для всего массива. Эта функция выполняется поверх (@) функции integer. В результате
allintegers 10 20 0.5
дает 0, а
allintegers 10 20
дает 1. Теперь напишем тупейшую функцию, которая всегда возвращает строку IMPOSSIBLE:
IMPOSSIBLE =: 'IMPOSSIBLE'&[
[ очень похожа на ], только она возвращает свой первый (левый) аргумент, игнорируя правый. С помощью & мы "закрепляем" левый аргумент, и получаем функцию от одного аргумента, который она будет игнорировать и всегда выдавать 'IMPOSSIBLE'. Это выглядит немного странно, но из дальнейшего будет видно, зачем это нужно.
Теперь осталось свести это воедино. Для того, чтобы получить результат, нам надо подсчитать, сколько человек выходит из каждого автобуса (getout); затем, если все полученные числа - целые (allintegers), нам надо просуммировать положительные числа (sumofinteger), а если не целые - то напечатать IMPOSSIBLE. Пишем:
task2 =: (IMPOSSIBLE`sumofpositive@.allintegers) @ getout
@ getout
говорит нам о том, что выражение в скобках выполняется поверх getout. Интерес представляет выражение в скобках: в нем с помощью одинарной кавычки из двух функций IMPOSSIBLE и sumofpositive создается массив из двух функций. С помощью @. из этого массива извлекается (и выполняется) нужная функция (именно для этого нам и нужно, чтобы IMPOSSIBLE была такой же функцией, как и sumofpositive). А какая именно функция нужна - вычисляется с помощью allintegers. Если в массиве будет дробное число, allintegers вернет 0 и @. извлечет функцию IMPOSSIBLE; если все числа - целые, она вернет 1, и будет выполнена sumofpositive.
task2 25 21 14
возвращает 6, а
task2 20 20 21
возвращает IMPOSSIBLE (потому что в этом случае треть пассажира из последнего автобуса должна пересесть в первый, а вторая его треть - во второй).
Разумеется, то же самое можно записать в одну строчку, просто заменяя все имена функций их содержимым:
task2p =: (('IMPOSSIBLE'&[)`(+/ @ (] * (>&0)))@.(*./ @ (<. = ]))) @ (] - (+/ % #))
Хотелось бы обратить внимание на то, что во всей программе нет ни одной переменной - даже формальной. Это и есть function-level programming (не путать с functional pogramming).
Спасибо всем, кто дочитал до этого места.