Этот цикл статей посвящен изучению CoffeeScript, нового языка программирования, который построен поверх JavaScript и обеспечивает рациональный синтаксис. Код CoffeeScript компилируется в эффективный код JavaScript. Его можно использовать не только для выполнения JavaScript в Web-браузере, но и с такими технологиями, как Node.js для серверных приложений. В первой части мы установили компилятор CoffeeScript и использовали его для создания кода, который может работать в Web-браузере или на сервере.
Теперь приступим к изучению языка CoffeeScript на примерах задач программирования из Проекта Эйлера (см. раздел Ресурсы). В этих примерах используются функции, диапазоны, выделения (comprehensions), блочные операторы, массивы и некоторые аспекты CoffeeScript как объектно-ориентированного языка.
Загрузите исходный код, который будет использоваться в этой статье.
Функции, диапазоны и выделения
Первая задача из проекта Эйлера, которую мы попытаемся решить, это задача 6 (см. раздел Ресурсов), в которой требуется вычислить сумму квадратов первых натуральных чисел, квадрат суммы этих же чисел, а затем определить разность. Для решения этой задачи будет использоваться CoffeeScript Read-Evaluate-Print-Loop (REPL). В листинге 1 показан код и соответствующий результат выполнения REPL.
Листинг 1. Задача 6 в REPL
coffee> square = (x) -> x*x [Function] coffee> sum = (nums) -> nums.reduce (a,b) -> a+b [Function] coffee> diff = (nums) -> (square sum nums) - (sum nums.map square) [Function] coffee> diff [1..100] 25164150 |
Разбор
- В REPL определяем функцию. (Как говорилось в Части 1, CoffeeScript заимствует природу функционального программирования JavaScript, отбрасывая большую часть C-подобного синтаксиса JavaScript, который делает язык слишком громоздким с точки зрения элегантного функционального программирования.)
В примере из листинга 1 определяется функция
square, которая принимает один параметр и возвращает произведение этого параметра на самого себя (его квадрат). Затем REPL сообщает, что вы определили функцию. - Определяем другую функцию,
sum, которая принимает один параметр: массив. Для этого массиваsumвызывает методreduce.Метод
reduceне добавлен CoffeeScript, а входит в состав самого JavaScript (он появился в JavaScript 1.8). Этот метод подобен функции reduce языка Python или функции fold языка Haskell или Scala. Он принимает функцию и перебирает массив слева направо, применяя функцию к предыдущему полученному значению и следующему значению из массива. Благодаря компактному синтаксису CoffeeScript функцияreduceпроста в применении. В данном случае она описывается выражением(a,b) -> а + b. Это функция принимает два значения и складывает их, так что все элементы массива складываются друг с другом. - Создаем функцию
diff, которая принимает массив чисел, вычисляет два подвыражения, а затем вычитает одно из другого. Первое подвыражение перебирает массив, суммируя его элементы, а затем возводит результат в квадрат.Во многих случаях CoffeeScript позволяет опустить скобки во избежание всяческой путаницы. Например, запись
square sum numsэквивалентнаsquare(sum(nums)). Второе подвыражение применяет к массиву методmap, еще один метод из JavaScript 1.8, который принимает на вход другую функцию. Он применяет эту функцию к каждому элементу массива, создавая новый массив результатов. В примере из листинга 1 в качестве входного параметра для map используется функция возведения в квадрат, что приводит к созданию массива, элементы которого являются квадратами элементов входного массива. Затем мы просто передаем их функции sum для получения суммы квадратов. - Передаем массив чисел функции
diffдля решения задачи 6 с использованием диапазона[1..100].Этот диапазон эквивалентен массиву всех чисел от 1 до 100 включительно. Если нужно, чтобы он был исключающим, достаточно написать
[1...100]- с тремя точками вместо двух. Передавая это функцииdiff, получаем решение задачи 6.
Теперь вернемся немного назад и рассмотрим задачу Проекта Эйлера 1 (см. раздел Ресурсы), где нужно просуммировать все целые числа до 1000, кратные 3 или 5. Пожалуй, это самая простая задача Проекта Эйлера. Ее легко решить с применением функций и диапазонов, как в задаче 6. Однако применение функции выделения CoffeeScript дает более элегантное решение, показанное в листинге 2.
Листинг 2. Решение задачи 1 с использованием функции выделения
coffee> (n for n in [1..999] when n % 3 == 0 or n % 5 == 0).reduce (x,y) -> x+y 233168 |
Всегда приятно решить задачу одной строкой кода, а лаконичный синтаксис CoffeeScript помогает в этом. В решении, приведенном в листинге 2, с помощью выделения составляется список всех целых чисел, кратных 3 или 5. Из диапазона [1..999] выбираются только те значения, которые делятся на 3 или 5. Затем используется еще одна операция reduce для суммирования значений. REPL вычисляет эту строку кода и распечатывает решение.
В следующем разделе рассматриваются более сложные задачи и производится дальнейшее исследование CoffeeScript.
Блочные операторы, массивы и файлы
В задаче 4 Проекта Эйлера (см. раздел Ресурсы) требуется найти самый большой палиндром, полученный произведением двух трехзначных чисел. Существует много способов решения этой задачи; один из них показан в листинге 3.
Листинг 3. Проверка на палиндромы
isPal = (n) ->
digits = []
while n > 0
digits.push(n % 10)
n = Math.floor(n / 10)
len = digits.length
comps = [0..len/2].map (i) -> digits[i] == digits[len-i-1]
comps.reduce (a,b)-> a && b
vals = []
vals.push(x*y) for x in [100..999] for y in [100..999]
pals = vals.filter (n) -> isPal(n)
biggest = pals.reduce (a,b) -> Math.max(a,b)
console.log biggest
|
- Определим функцию
isPal, которая проверяет, является ли число палиндром. Эта функция несколько сложнее, чем те, что мы определяли до сих пор. Она состоит из семи строк кода.Вы, наверное, заметили, что в CoffeeScript не используются фигурные скобки ({}) или какой-либо другой явный механизм для обозначения начала и конца функции. В нем применяются пробелы (отступы), как в Python, и все функции начинаются одинаково: со списка параметров, оканчивающегося стрелкой (->). Затем мы создаем пустой массив для цифр числа и начинаем цикл while. Этот цикл схож с циклом while JavaScript (или C, Java и т.п.). Предикат (
n > 0) не нужно заключать в круглые скобки. Тело цикла смещается вправо, что указывает на то, что это часть цикла. Внутри цикла мы отделяем от числа последнюю цифру, перемещаем ее к началу массива и делим число на 10, отбрасывая остаток. Это приводит к созданию массива цифр исходного числа. Цикл можно заменить просто выражениемdigits = new String n, которое преобразуетnв строку. Остальная часть кода работает как есть. - Получив массив цифр, мы создаем массив в половину длины этого массива. Функция
mapпревращает каждый элемент массива в логическое значение, указывающее, равны ли цифры, равноудаленные от начала и от конца массива.Если да, то это палиндром. Чтобы проверить результат, используем другую функцию reduce, на этот раз вычисляя логическое произведение.
- Теперь, когда у нас определена функция
isPal, используем ее для тестирования своих палиндромов. Проверим все числа, которые являются произведением двух трехзначных чисел.- Создадим два выделения, каждое из которых начинается с наименьшего трехзначного числа (100) и заканчивается наибольшим (999).
- Для каждого выделения вычислим произведение и поместим его в массив.
- Воспользуемся другой функцией
reduce, чтобы найти наибольший элемент массива. Наконец, распечатаем этот элемент с помощью console.log. - Сохраним его в файле.
В листинге 4 показано, как выполнить код и зафиксировать время решения.
Листинг 4. Решение задачи 4
$ time coffee p4.coffee 906609 real 0m2.132s user 0m2.106s sys 0m0.022s |
Сценарий из листинга 4 выполняется на быстром компьютере более двух секунд, во многом за счет сложного выделения, которое создает 899*899 = 808 201 проверяемых значений (многие из них - дубликаты). В качестве дополнительного упражнения код листинга 3 можно оптимизировать.(Подсказка: преобразование числа в строку выполняется намного быстрее.)
Задача 22 Проекта Эйлера (см. раздел Ресурсы) требует выполнения сложных вычислений над списком строк. Список считывается из файла, анализируется, сортируется, и каждая строка преобразуется в число, а затем суммируются произведения всех чисел на номера их позиции в списке. Задача 22 позволяет увидеть, как работают файлы в CoffeeScript. В ней применяются также некоторые манипуляции со строками и дополнительные трюки с массивами. Решение приведено в листинге 5.
Листинг 5. Работа с файлами в CoffeeScript
path = "/path/on/your/computer/names.txt"
fs = require "fs"
contents = fs.readFileSync path, "utf8"
strs = contents.split(",")
names = strs.map (str) -> str[1 .. str.length - 2]
names = names.sort()
vals = names.map (name) -> name.split("")
vals = vals.map (list) ->
list.map (ch) -> 1 + ch.charCodeAt(0) - 'A'.charCodeAt(0)
vals = vals.map (list) ->
list.reduce (a,b) -> a+b
vals = ((i+1)*vals[i] for i in [0...vals.length])
total = vals.reduce (a,b) -> a+b
console.log total
|
- Сохранте файл. В описании задачи есть ссылка, или можно использовать исходный код, прилагаемый к этой статье. Замените значение переменной path на абсолютный путь к файлу в вашем компьютере.
CoffeeScript не содержит никаких специальных библиотек для работы с файлами. Вместо этого используется node.js и его модуль
fs. Загрузите модульfsс помощью функцииrequire. - Прочтите содержимое файла с помощью
fs.readFileSync. Файл содержит имена типа "MARY", "PATRICIA" и т.п. Он записан в одну строку, так что мы используем методsplitдля разделения его по запятым.В начале и в конце каждой строки остаются кавычки ("). Чтобы избавиться от них, используем функцию
mapи заменим каждую строку вырезкой. Еслиstr- строка, тоstr[1 .. str.length -2]- подстрока, которая начинается со второго символа и заканчивая предпоследним. Код будет в точности удалять первый и последний символы – то есть те самые кавычки. Вырезание строк может быть очень полезным. - Получив список строк без кавычек, мы готовы к сортировке. Используем метод
sort. Необходимо преобразовать строки в число, заменяя каждый символ номером его позиции в алфавите (A -> 1, B -> 2, C -> 3 и т.д.).- Опять преобразуем каждую строку в массив символов с помощью метода
split. - Затем с помощью метода
charCodeAtпоставим в соответствие каждому символу числовое значение. - Сложим эти числовые значения с помощью еще одной операции
reduce.
- Опять преобразуем каждую строку в массив символов с помощью метода
- Умножим каждое число на его позицию в списке и просуммируем с помощью другого выделения. Создадим новый массив, каждый элемент которого получается путем умножения значения элемента из предыдущего массива на номер его позиции. Просуммируем элементы этого массива с помощью еще одной операции
reduceи распечатаем сумму.
Опять же, можно сохранить результат в файле, а затем выполнить его с измерением времени, как показано в листинге 6.
Листинг 6. Время выполнения задачи 22
$ time coffee p22.coffee 871198282 real 0m0.133s user 0m0.115s sys 0m0.013s |
Решение задачи 22 занимает менее 0,2 с. Для выполнения необходимых расчетов потребовалось почти столько же строк кода, сколько их в описании хода вычислений на обычном языке. Это хороший пример лаконичного синтаксиса CoffeeScript. Можно предположить, что на других языках программирования для этого примера потребуется намного больше строк кода.
В этом разделе мы решили более сложную задачу с использованием некоторых важных особенностей CoffeeScript. В следующем рассматривается важнейшее свойство CoffeeScript: объектно-ориентированное программирование.
Объектно-ориентированный CoffeeScript
Как упоминалось в первой части, основной недостаток JavaScript заключается в его «нестандартном» стиле объектно-ориентированного программирования (OOП). В подавляющем большинстве случаев применяется ООП на основе классов. CoffeeScript обеспечивает ООП, основанный на классах.
Следующая задача состоит в использовании ООП CoffeeScript для решения задачи 35 Проекта Эйлера (см. раздел ресурсы). В задаче 35 говорится, что круговые простые числа — это особый вид простых чисел, для которых все перестановки цифр из конца в начало являются простыми числами. В коде, приведенном в листинге 7, ООП используется для вычисления количества круговых простых чисел меньше миллиона.
Листинг 7. Подсчет круговых простых чисел
class PrimeSieve
constructor: (@max) ->
@nums = [2..@max]
for p in @nums
d = 2*p
while p != 0 and d <= @max
@nums[d-2] = 0
d += p
isPrime: (n) -> @nums[n-2] != 0
thePrimes: -> @nums.filter (n) -> n != 0
class CircularPrimeGenerator extends PrimeSieve
genPerms = (num) ->
s = new String num
x = (for i in [0 ... s.length]
s[i+1 ... s.length].concat s[0..i])
x.map (a) -> parseInt(a)
isCircularPrime : (n) ->
perms = genPerms(n)
len = perms.length
primePerms = perms.filter (p) => @isPrime(p)
len == primePerms.length
theCircularPrimes: ->
(p for p in @thePrimes() when @isCircularPrime(p))
max = process.argv[2]
generator = new CircularPrimeGenerator max
console.log "Number of circular primes less than #{max} is
#{generator.theCircularPrimes().length}"
|
- Создаем класс
PrimeSieve, который реализует классический алгоритм вычисления всех простых чисел до определенного значения "Решето Эратосфена".Знак
@означает свойство класса, а также сокращение выражения'this.'. Таким образом, запись@nums = [2..@max]эквивалентнаthis.nums = [2.. this.max].Методы класса указаны их именем, за которым следует точка с запятой и определение функции. Первый метод, называемый
constructor, - это конструктор класса. К примеру, новыйPrimeSieve(100)приведет к вызову конструктора со значениемmaxравным 100, которое присваивается параметруthis.max. Наш конструктор строит решето и сохраняет простые числа в переменной экземпляра@nums. Затем объявляются еще два метода:isPrimeиthePrimes. МетодthePrimesиспользует фильтр массива для удаления из@numsсоставных чисел. - Объявляем подкласс
CircularPrimeGeneratorклассаPrimeSieve. Как и во многих популярных языках ООП, в CoffeeScript используется синтаксисclass ... extends. Этот класс будет наследовать конструктор, переменные экземпляров и методыPrimeSieve. Он содержит:-
метод
genPermsдля генерации всех круговых перестановок цифр данного числа; -
метод
isCircularPrime, который создает все перестановки данного числа и удаляет из списка перестановок все составные числа. Если отфильтрованный список содержит все те же элементы, что и неотфильтрованный, то это круговое простое число; - метод
theCircularPrimes, который создает список всех круговых простых чисел с помощью выделения.
@thePrimes, определенный в суперклассе, а затем просто отфильтровывать некруговые простые числа.Когда оба класса определены, их можно использовать для решения этой задачи.
-
метод
-
В листинге 7 принимается аргумент командной строки, указывающий максимальное значение для вычисления круговых простых чисел. Все аргументы командной строки можно получить с помощью
process.argv. Первые два значения этого массива - это команда и сценарий, так что первый параметр для использования в сценарии содержится вprocess.argv[2].Создадим экземпляр
CircularPrimeGenerator, используя максимальное значение, переданное в сценарий.распечатаем число найденных круговых простых чисел с помощью console.log.
В этом примере используется еще одна удобная особенность CoffeeScript: интерполяция строк для создания строки, которая передается в console.log.
В этой статье рассмотрены многие особенности CoffeeScript. Она кратко описывает синтаксис и особенности языка программирования, которые позволяют изящно реализовать многие распространенные алгоритмы. В то же время CoffeeScript упрощает объектно-ориентированное программирование. С какой бы задачей вы не столкнулись, синтаксис CoffeeScript упростит ее решение.
Следующая статья этого цикла будет более прагматичной и посвящена использованию CoffeeScript для клиентских сценариев Web-приложений.
| Описание | Имя | Размер | Метод загрузки |
|---|---|---|---|
| Исходный код примера для статьи | cs2.zip | 19 КБ | HTTP |
- Оригинал статьи
- Цикл статей Ваша первая чашечка CoffeeScript (developerWorks): изучение популярного языка программирования CoffeeScript, построенного поверх JavaScript.
- Ваша первая чашечка CoffeeScript: Часть 1. Приступаем к работе (developerWorks, декабрь 2011 г.): об удобстве CoffeeScript для разработчиков, установке компилятора CoffeeScript и его использовании для создания кода, который может работать в Web-браузере или на сервере.
-
Проект Эйлера: изучение сложных математических задач и задач программирования, для решения которых недостаточно одной математической интуиции.
- Задача 1 Проекта Эйлера: сложить все натуральные числа меньше 1000, кратные 3 или 5.
- Задача 4 Проекта Эйлера: найти самый большой палиндром, полученный произведением двух трехзначных чисел.
- Задача 6 Проекта Эйлера: определить разность между суммой квадратов и квадратом суммы.
- Задача 22 Проекта Эйлера: найти сумму очков в файле имен.
- Just what is Node.js? (developerWorks, май 2011 г.): об интерпретаторе JavaScript на стороне сервера Node, меняющем представление о том, как должен работать сервер.
-
Node.js: отправная точка для дальнейшего изучения приложения.
- Use Node.js as a full cloud environment development stack (developerWorks, апрель 2011 г.): о том, как Node.js сочетается с облачными технологиями.
- High-performance Web development with Google Web Toolkit and Eclipse (developerWorks, октябрь 2009 г.): идея компиляции в JavaScript не нова. Поклонникам языка программирования Java следует прочесть эту статью.
- All aboard! An introduction to Rails 3 (developerWorks, март 2010 г.): CoffeeScript теперь является частью Ruby on Rails. Эта статья знакомит с другими новыми особенностями Rails.
-
Проект CoffeeScript на Github: отправная точка для изучения CoffeeScript.
- Create Ajax applications for the mobile Web (developerWorks, март 2010 г.): дополнительные сведения об использовании Ajax в мобильных Web-приложениях.