appendix B
Answers to exercises
Exercise 2.3.3
torch = price: 21
umbrella = {}
combinedCost = (torch.price || 0) + (umbrella.price || 0)
# 21
Exercise 2.4.4
animal = "crocodile"
collective = switch animal
when "antelope" then "herd"
when "baboon" then "rumpus"
when "badger" then "cete"
when "cobra" then "quiver"
when "crocodile" then "bask"
# bask
Exercise 2.5.3
animal = "cobra"
collective = switch animal
when "antelope" then "herd"
when "baboon" then "rumpus"
when "badger" then "cete"
when "cobra" then "quiver"
when "crocodile" then "bask"
"The collective of #{animal} is #{collective}"
# The collective of cobra is quiver
Exercise 2.6.5
animals = 'baboons badgers antelopes cobras crocodiles'
result = for animal in animals.split " "
collective = switch animal
when "antelopes" then "herd"
when "baboons" then "rumpus"
when "badgers" then "cete"
when "cobras" then "quiver"
when "crocodiles" then "bask"
"A #{collective} of #{animal}"
Exercise 3.1.5
countWords = (text) ->
words = text.split /[\s,]/
significantWords = (word for word in words when word.length > 3)
significantWords.length
everyOtherWord = (text) ->
words = text.split /[\s,]/
takeOther = for word, index in words
if index % 2 then ""
else word
takeOther.join(" ").replace /\s\s/gi, " "
Exercise 3.3.4
http = require 'http'
fs = require 'fs'
sourceFile = 'attendees'
fileContents = 'File not read yet.'
readSourceFile = ->
fs.readFile sourceFile, 'utf-8', (error, data) ->
if error
console.log error
else
fileContents = data
fs.watchFile sourceFile, readSourceFile
countWords = (text) ->
text.split(/,/gi).length
readSourceFile sourceFile
server = http.createServer (request, response) ->
response.end "#{countWords(fileContents)}"
server.listen 8080, '127.0.0.1'
Exercise 3.4.4
accumulate = (initial, items, accumulator) ->
total = initial
for item in items
total = accumulator total, item
total
sumFractions = (fractions) ->
accumulator = (lhs, rhs) ->
if lhs is '0/0'
rhs
else if rhs is '0/0'
lhs
else
lhsSplit = lhs.split /\//gi
rhsSplit = rhs.split /\//gi
lhsNumer = 1*lhsSplit[0]
lhsDenom = 1*lhsSplit[1]
rhsNumer = 1*rhsSplit[0]
rhsDenom = 1*rhsSplit[1]
if lhsDenom isnt rhsDenom
commonDenom = lhsDenom*rhsDenom
else
commonDenom = lhsDenom
sumNumer = lhsNumer*(commonDenom/lhsDenom) + rhsNumer*(commonDenom/rhsDenom)
"#{sumNumer}/#{commonDenom}"
accumulate '0/0', fractions, accumulator
console.log sumFractions ['2/6', '1/4']
# '14/24'
keep = (arr, cond) ->
item for item in arr when cond item
Exercise 3.4.4
phonebook =
numbers:
hannibal: '555-5551'
darth: '555-5552'
hal9000: 'disconnected'
freddy: '555-5554'
'T-800': '555-5555'
list: ->
"#{name}: #{number}" for name, number of @numbers
add: (name, number) ->
if not (name of @numbers)
@numbers[name] = number
else
"#{name} already exists"
edit: (name, number) ->
if name of @numbers
@numbers[name] = number
else
"#{name} not found"
get: (name) ->
if name of @numbers
"#{name}: #{@numbers[name]}"
else
"#{name} not found"
console.log "Phonebook. Commands are add, get, edit, list, and exit."
process.stdin.setEncoding 'utf8'
stdin = process.openStdin()
stdin.on 'data', (chunk) ->
args = chunk.split ' '
command = args[0].trim()
name = args[1].trim() if args[1]
number = args[2].trim() if args[2]
switch command
when 'add'
res = phonebook.add(name, number) if name and number
console.log res
when 'get'
console.log phonebook.get(name) if name
when 'edit'
console.log phonebook.edit(name, number) if name and number
when 'list'
console.log phonebook.list()
when 'exit'
process.exit 1
css = (element, styles) ->
element.style ?= {}
for key, value of styles
element.style[key] = value
class Element
div = new Element
css div, width: 10
div.style.width
# 10
Exercise 4.6.3
cassette =
title: "Awesome songs. To the max!"
duration: "10:34"
released: "1988"
track1: "Safety Dance - Men Without Hats"
track2: "Funkytown - Lipps, Inc"
track3: "Electric Avenue - Eddy Grant"
track4: "We built this city - Starship"
musicDevice = Object.create cassette
secondMusicDevice = Object.create musicDevice
cassette.track5 = "Hello - Lionel Richie"
secondMusicDevice.track5
# "Hello - Lionel Richie"
musicDevice.track6 = "Mickey - Toni Basil"
secondMusicDevice.track6
# "Mickey - Toni Basil"
Exercise 4.7.2
views =
excluded: []
pages: {}
clear: ->
@pages = {}
increment: (key) ->
unless key in @excluded
@pages[key] ?= 0
@pages[key] = @pages[key] + 1
ignore: (page) ->
@excluded = @excluded.concat page
total: ->
sum = 0
for own page, count of @pages
sum = sum + count
sum
Exercise 4.8.3
class GranTurismo
constructor: (options) ->
@options = options
summary: ->
("#{key}: #{value}" for key, value of @options).join "\n"
options =
wheels: 'phat'
dice: 'fluffy'
scruffysGranTurismo = new GranTurismo options
scruffysGranTurismo.summary()
# wheels: phat
# dice: fluffy
class GranTurismo
constructor: (@options) ->
summary: ->
("#{key}: #{value}" for key, value of @options).join "\n"
Exercise 5.3.3
class Product
# any implementation of Product
class Camera extends Product
cameras = []
@alphabetical = ->
cameras.sort (a, b) -> a.name > b.name
constructor: ->
all.push @
super
Exercise 5.8.1
fs = require 'fs'
http = require 'http'
url = require 'url'
coffee = require 'coffee-script'
class ShopServer
constructor: (@host, @port, @shopData, @shopNews) ->
@css = ''
fs.readFile './client.css', 'utf-8', (err, data) =>
if err then throw err
@css = data
readClientScript: (callback) ->
script = "./client.coffee"
fs.readFile script, 'utf-8', (err, data) ->
if err then throw err
callback data
headers: (res, status, type) ->
res.writeHead status, 'Content-Type': "text/#{type}"
renderView: ->
"""
<!doctype html>
<head>
<title>Agtron's Emporium</title>
<link rel='stylesheet' href='/css/client.css' />
</head>
<body>
<div class='page'>
<h1>----Agtron’s Emporium----</h1>
<script src='/js/client.js'></script>
</div>
</body>
</html>
"""
handleClientJs: (path, req, res) ->
@headers res, 200, 'javascript'
writeClientScript = (script) ->
res.end coffee.compile(script)
@readClientScript writeClientScript
handleClientCss: (path, req, res) ->
@headers res, 200, 'css'
res.end @css
handleImage: (path, req, res) ->
fs.readFile ".#{path}", (err, data) =>
if err
@headers res, 404, 'image/png'
res.end()
else
@headers res, 200, 'image/png'
res.end data, 'binary'
handleJson: (path, req, res) ->
switch path
when '/json/list'
@headers res, 200, 'json'
res.end JSON.stringify(@shopData)
when '/json/list/camera'
@headers res, 200, 'json'
camera = @shopData.camera
res.end JSON.stringify(camera)
when '/json/news'
@headers res, 200, 'json'
res.end JSON.stringify(@shopNews)
else
@headers res, 404, 'json'
res.end JSON.stringify(status: 404)
handlePost: (path, req, res) ->
category = /^\/json\/purchase\/([^/]*)\/([^/]*)$/.exec(path)?[1]
item = /^\/json\/purchase\/([^/]*)\/([^/]*)$/.exec(path)?[2]
if category? and item? and data[category][item].stock > 0
data[category][item].stock -= 1
@headers res, 200, 'json'
res.write JSON.stringify
status: 'success',
update: data[category][item]
else
res.write JSON.stringify
status: 'failure'
res.end()
handleGet: (path, req, res) ->
if path is '/'
@headers res, 200, 'html'
res.end @renderView()
else if path.match /\/json/
@handleJson path, req, res
else if path is '/js/client.js'
@handleClientJs path, req, res
else if path is '/css/client.css'
@handleClientCss path, req, res
else if path.match /^\/images\/(.*)\.png$/gi
@handleImage path, req, res
else
@headers res, 404, 'html'
res.end '404'
start: ->
@httpServer = http.createServer (req, res) =>
path = url.parse(req.url).pathname
if req.method == "POST"
@handlePost path, req, res
else
@handleGet path, req, res
@httpServer.listen @port, @host, =>
console.log "Running at #{@host}:#{@port}"
stop: ->
@httpServer?.close()
data = require('./data').all
news = require('./news').all
shopServer = new ShopServer '127.0.0.1', 9999, data, news
shopServer.start()
Exercises 7.2.5
swapPairs = (array) ->
for index in array by 2
[first, second] = array[index-1..index]
[second, first]
swapPairs([3,4,3,4,3,4])
# [ [ 4, 3 ], [ 4, 3 ], [ 4, 3 ] ]
swapPairs([1,2,3,4,5,6])
# [ [ 2, 1 ], [ 4, 3 ], [ 6, 5 ] ]
swapPairs = (array) ->
reversedPairs = for index in array by 2
[first, second] = array[index-1..index]
[second, first]
[].concat reversedPairs...
swapPairs([3,4,3,4,3,4])
# [ 4, 3, 4, 3, 4, 3 ]
swapPairs([1,2,3,4,5,6])
# [ 2, 1, 4, 3, 6, 5 ]
phoneDirectory =
A: [
name: 'Abe'
phone: '555 1110'
,
name: 'Andy'
phone: '555 1111'
,
name: 'Alice'
phone: '555 1112'
]
B: [
name: 'Bam'
phone: '555 1113'
]
lastNumberForLetter = (letter, directory) ->
[..., lastForLetter] = directory[letter]
{phone} = lastForLetter
phone
lastNumberForLetter 'A', phoneDirectory
# 555 1112
Exercises 10.4.4
class Tracking
constructor: (prefs, http) ->
@http = http
start: ->
@http.listen()
http =
listen: ->
double = (original) ->
mock = {}
for key, value of original
if value.call?
do ->
stub = ->
stub.called = true
mock[key] = stub
mock
double = (original) ->
spy = Object.create original
for key, value of original
if value.call?
do ->
originalMethod = value
spyMethod = (args...) ->
spyMethod.called = true
originalMethod args...
spy[key] = spyMethod
spy