Your first cup of CoffeeScript, Part 2: Learn the language with hands-on examples

This series explores the popular CoffeeScript programming language, which is built on top of JavaScript. In Part 1, you learned about the perks for developers, set up the CoffeeScript compiler, and used it to create code that was ready to run in a browser or server. In this article, wade deeper into the CoffeeScript language. Use CoffeeScript to solve several programming problems, with a mathematical flavor to them, from Project Euler. Example source code is provided.

Share:

Michael Galpin, Software Engineer, Google

Michael Galpin's photoMichael Galpin is a software engineer at Google. He is a co-author of the book Android in iPractice and a frequent contributor to developerWorks. For a sneak peek at what he's up to next, check out his blog or follow him on Twitter @michaelg or Google+ Michael Galpin.



07 February 2012

Also available in Chinese Russian Japanese Vietnamese

Introduction

This series explores CoffeeScript, a new programming language that is built on top of JavaScript and offers a clean syntax. CoffeeScript compiles into efficient JavaScript. In addition to running the JavaScript in a web browser, you can use it with technologies such as Node.js for server applications. In Part 1, you learned to set up the CoffeeScript compiler and use it to create code that's ready to run in a browser or server.

In this article, explore the CoffeeScript language by solving several programming problems from Project Euler (see Resources for more information about Project Euler). The examples walk you through the use of functions, ranges, comprehensions, block statements, arrays, and some object-oriented aspects of CoffeeScript.

Download the source code used in this article.


Functions, ranges, and comprehensions

The first Project Euler problem you'll tackle is Problem 6 (see Resources), which asks you to calculate the sum of the squares of the first natural numbers, the square of the sum of those same numbers, and then give the difference. For this problem you will use the CoffeeScript Read-Evaluate-Print-Loop (REPL). Listing 1 shows the code and corresponding output from the REPL session.

Listing 1. Problem 6 from the 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

Breakdown:

  1. In the REPL, define a function. (As mentioned in Part 1, CoffeeScript celebrates the functional programming nature of JavaScript, discarding much of JavaScript's C-like syntax that makes it cumbersome to do elegant functional programming.)

    The example in Listing 1 defines a function called square and states that it takes a single parameter and returns the product of multiplying that parameter by itself (squaring it). The REPL then tells you that you've defined a function.

  2. Define another function called sum that takes a single parameter: an array. Sum then invokes the reduce method on the array.

    The reduce method is not added by CoffeeScript but is part of JavaScript proper (added in JavaScript 1.8). The reduce method is similar to the reduce function in Python or the fold function in Haskell or Scala. It takes a function and goes from left to right on the array, applying the function to the previous value returned by the fold and the next value in the array. CoffeeScript's compact function syntax makes reduce easy to use. In this case, the function passed to reduce is specified by (a,b) -> a + b. This is a function that takes two values and adds them together, thus causing all of the elements of the array to be added together.

  3. Create a function called diff, which takes an array of numbers, calculates two sub-expressions, then subtracts them. The first passes the array to the sum function and then takes the result and passes it to the square function.

    CoffeeScript allows you to omit parentheses in many cases so there can be no confusion. For example, square sum nums is equivalent to square(sum(nums)). The second sub-expression invokes the map method of the array, which is another JavaScript 1.8 method that takes another function as its input. It then applies that function to each member of the array, creating a new array from the results. The example in Listing 1 uses the square function as the input parameter to map, giving you an array whose elements are the squares of the input array. Then, you simply pass this to the sum function to get the sum of the squares.

  4. Pass the appropriate array of numbers into the diff function to solve Problem 6, using a range [1..100].

    This range is equivalent to an array of all of the numbers from 1 to 100, inclusive. If you had wanted it to be exclusive you would have written [1...100], using three periods instead of two. Passing this to diff gives you the solution to Problem 6.

Let's back up a little and look at Project Euler Problem 1 (see Resources), which asks you to sum up all of the integers less than 1000 that are divisible by either 3 or 5. You might think this would be the easiest of the Project Euler problems. You could easily solve it just using functions and ranges, as in Problem 6. With the CoffeeScript comprehensions feature, however, you can create the elegant solution in Listing 2.

Listing 2. Solving Problem 1 using comprehensions
coffee> (n for n in [1..999] when n % 3 
== 0 or n % 5 == 0).reduce (x,y) -> x+y
233168

It's always nice to solve a problem with a single line of code, and CoffeeScript's concise syntax lends itself to one-liners. The solution in Listing 2 creates a list of all of the integers that are multiples of 3 or 5 by using a comprehension. It is generated by starting with the range [1..999] but using only the values that are divisible by 3 or 5. Another reduce is then used to sum the values up. The REPL evaluates this one line of code and prints the solution to the problem.

The next section tackles slightly more difficult problems and explores CoffeeScript further.


Block statements, arrays, and files

Project Euler Problem 4 (see Resources) asks you to find the largest palindrome that is the product of two three-digit numbers. There are many ways to solve this problem; Listing 3 shows one way.

Listing 3. Testing for palindromes
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
  1. Define a function called isPal, which will test if a number is a palindrome. This function is a little more complicated than those defined so far. It has seven lines of code in its body.

    You probably noticed that CoffeeScript does not use curly brackets ({ }) or any other explicit mechanism to denote the beginning and ending of the function. It uses whitespace (indentation), much like Python, and starts with the same notation to denote a function: the parameter list followed by the greater than arrow (->). Then you create an empty array for the digits of the numbers and begin a while loop. This loop is similar to the while loop from JavaScript (or C, Java, and so on). The predicate (n > 0) does not require parentheses around it. The body of the loop is indented further to indicate that it is part of the loop. Inside the loop you chop off the last digit of the number, push it to the front of the array, and then divide the number by 10, discarding the remainder. This results in an array of the digits of the original number. You could also replace the loop simply with digits = new String n, converting n to a string. The rest of the code works as-is.

  2. After you have the array of digits, create an array that is half the length of this array. Use the map function to turn each element of this array into a Boolean value corresponding to whether the digits equidistant from the beginning and ending of the array are equal.

    If all of these come out true, then you have a palindrome. To test the work, simply use another reduce function, this time 'and-ing' together the Booleans.

  3. Now that you have the isPal function defined, use it to test for palindromes. Test all of the numbers that are the product of two three-digit numbers.
    1. Create two comprehensions, each ranging from the smallest three-digit number (100) to the largest (999).
    2. For each of these, take the product and put it into an array.
    3. Use another reduce function to find the largest element in the array. Finally, this element is printed out using console.log.
    4. Save this to a file.

Listing 4 shows how to execute and time the solution.

Listing 4. Executing the solution for Problem 4
$ time coffee p4.coffee
906609

real	0m2.132s
user	0m2.106s
sys	0m0.022s

The script in Listing 4 takes over two seconds to execute on a fast computer, largely due to the compound comprehension that generates 899*899 = 808,201 values to test (many of them duplicates). As an additional exercise, you can optimize the Listing 3 code. (Hint: Converting the number to a string is actually much faster.)

Project Euler Problem 22 (see Resources) requires you to perform a complex calculation on a list of strings. You will read the list from a file, parse the contents into a list, sort it and convert each string to a number, and then sum the products of each number and its position in the list. Problem 22 lets you see how files work in CoffeeScript. There's also some string manipulation and more array magic. Listing 5 shows the solution.

Listing 5. Working with files in 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
  1. Save the file. There's a link in the problem description, or you can use the source code included with this article. Change the value of the path variable to the absolute path to the file on your computer.

    CoffeeScript does not include any special libraries for working with files. Instead, it leverages node.js and its fs module. Load the fs module using the require function.

  2. Read the contents of the file using fs.readFileSync. The file looks like "MARY", "PATRICIA", and so on. It starts out as a single string, so use the split method to break it apart at the commas.

    Each string still has double quotes (") at the beginning and end of the string. To get rid of them, use the map function and replace each string with a slice. If str is a string, then str[1 .. str.length -2] is a substring starting with the second character and ending with the next to last character. The code will precisely remove the first and last characters—those pesky double quotes. Slicing strings can be very convenient.

  3. After you have a list of strings without double quotes, you're ready to sort. Use array's sort method. You need to convert the strings to a number where each character becomes its place in the alphabet (A -> 1, B -> 2, C -> 3, and so on).
    1. Turn each string into an array of characters using the split method again.
    2. Map each character to a numeric value using the charCodeAt method.
    3. Add up these numeric values using another reduce operation.
    The list of strings is transformed into a list of numbers.
  4. Multiply each number by its position in the list and sum these together using another comprehension. Create a new array where each element is generated by multiplying an element from the previous array along by its position. Sum up the elements of this array by using one more reduce operation, and print out the total.

Once again, you can save the work to a file, and then execute and time it, as shown in Listing 6:

Listing 6. Timing the execution of Problem 22
$ time coffee p22.coffee
871198282

real	0m0.133s
user	0m0.115s
sys	0m0.013s

It takes less than 0.2 seconds to execute the solution to Problem 22. It took almost as many lines of English to describe what was to be calculated as it did lines of code to perform the calculation. This is a great example of CoffeeScript's succinct syntax. You can imagine the example taking many more lines of code in other programming languages.

In this section, you have solved more difficult problems using some of the important features of CoffeeScript. The next section examines a crucial feature in CoffeeScript: object-oriented programming.


Object-oriented CoffeeScript

As mentioned in Part 1, a major pain point of JavaScript is its "unusual" style of object-oriented programming (OOP). It is unusual simply because class-based OOP is so overwhelmingly common. CoffeeScript makes class-based OOP.

Your next challenge is to use CoffeeScript's OOP to solve Project Euler Problem 35 (see Resources). In Problem 35, you're told that a circular prime is a special kind of prime number with the property that you can perform any rotation of its digits and still have a prime number. The code in Listing 7 uses OOP to calculate the number of circular primes less than one million.

Listing 7. Counting circular primes
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}"
  1. Create a class called PrimeSieve, which implements the classic Sieve of Erasthones algorithm for calculating all of the primes less than a particular value.

    The @ sign denotes a property of a class and is also shorthand for 'this.'. Therefore, @nums = [2..@max] is equivalent to this.nums = [2 .. this.max].

    Class methods are indicated by their name followed by a semicolon and the function definition. The first method, called constructor, is a constructor for the class. For example, new PrimeSieve(100) would cause the constructor to be invoked with 100 passed in as max and assigned to this.max. Our constructor builds the sieve and stores the primes in the @nums member variable. It then declares two more methods: isPrime and thePrimes. The thePrimes method uses an array filter to remove the composite numbers from @nums.

  2. Declare a subclass of PrimeSieve, called CircularPrimeGenerator. CoffeeScript uses class ... extends syntax, like many popular OOP languages. This class will inherit the constructor, member variables, and methods of PrimeSieve. It has the:
    • genPerms method for generating all of the permutations of a given number by rotating the digits of the number.
    • isCircularPrime method that generates all of the permutations of a given number and removes any numbers in the list of permutation that are not a prime. If the filtered list has all of the same elements as the unfiltered list, then the number must be a circular prime.
    • theCircularPrimes method that generates a list of all of the circular primes using a comprehension.
    Notice how you're able to use the @thePrimes method defined in the superclass and then simply filter out the primes that are not circular.

    After you have two classes defined, you're ready to use them to solve the problem.

  3. Listing 7 was written to take a command-line argument that is the max value to be used for calculating the primes and the circular primes. All command-line arguments can be accessed using process.argv. The first two values in this array are the command and the script, so it is process.argv[2] that contains the first parameter to be used by the script.

    Create an instance of CircularPrimeGenerator using the max value passed in to the script.

    Print out the number of circular primes that were found using console.log.

In this example, you used another convenient feature of CoffeeScript: string-interpolation to create the string that is passed to console.log.


Conclusion

In this article, you explored many of the features of CoffeeScript. Its concise syntax and functional programming features let you elegantly implement many common algorithms. At the same time, CoffeeScript also provides simplified object-oriented programming. For whatever kind of problem you need to solve, you'll find that CoffeeScript's syntax simplifies your task.

Stay tuned for the next part of this series, which will get more pragmatic and use CoffeeScript for client-side scripting in a web application.


Download

DescriptionNameSize
Article source codecs2.zip19KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=791365
ArticleTitle=Your first cup of CoffeeScript, Part 2: Learn the language with hands-on examples
publish-date=02072012