In this chapter, you will work with lists and vectors, along with the map
, filter
, and reduce
functions. All of these take functions as one of their arguments, and are thus higher-order functions.
This is a quick warm-up étude: Given a list of integers that have zeros interspered throughout, move all the zeros to the end. Name the function move-zeros
; it accepts a list as an argument and returns a new list with the zeros at the end. I saw the problem at this page, solved in Java, and wondered if I could do it in ClojureScript. Answer: Yes, I could. And so can you. Hint: filter
is useful. After I solved it, I realized just how much my thinking about functional programming had changed the way I look at imperative code. You may have the same experience.
move-zeros.core=> (move-zeros [1 0 0 2 0 3 0 4 5 0 6]) (1 2 3 4 5 6 0 0 0 0 0)
See a suggested solution: Solution 3-1
Write a function named ordinal-day
that takes a day, month, and year as its three arguments and returns the ordinal (Julian) day of the year. Bonus points if you return zero for invalid dates such as 29-02-2015 or 40-40-2015. Don’t worry about handling dates before the year 1584 correctly.
You will need to know if a year is a leap year or not. I’ll give you that one for free:
(defn leap-year? "Return true if given year is a leap year; false otherwise" [year] (or (and (= 0 (rem year 4)) (not= 0 (rem year 100))) (= 0 (rem year 400))))
Some sample output from the REPL:
formulas.core=> (ordinal-day 1 3 2015) 60 formulas.core=> (ordinal-day 1 3 2016) 61 formulas.core=> (ordinal-day 1 13 2015) 0 formulas.core=> (ordinal-day 29 2 2015) 0 formulas.core=> (ordinal-day 29 2 2016) 60 formulas.core=> (ordinal-day 31 9 2015) 0
Then, modify the daylight calculator from Interacting With JavaScript and Web Pages to allow entry of a date in the form yyyy-mm-dd. You will need to split the input data into individual numbers. You could use the split
method for JavaScript strings, or you can use the split
method from the clojure.string
library. If you want to use the latter method, you will need to add
that library to your require
:
(
ns
stats.core
(
:require
[
clojure.browser.repl
:as
repl
]
<strong>
[
clojure.string
:as
str
]
</strong>
))
To specify a regular expression for split
, prefix a string with #
. Here is some sample output from the REPL. Using JavaScript’s split
returns a JavaScript array. Notice that you do not need to escape backslashes in patterns (see the last example).
formula.core=> (require '[clojure.string :as str]) nil formula.core=> (.split "a:b:c:d" #":") #js ["a" "b" "c" "d"] formula.core=> (str/split "a:b:c:d" #":") ["a" "b" "c" "d"] formula.core=> (str/split "abc123def456ghi789jkl" #"\d+") ["abc" "def" "ghi" "jkl"] formula.core=>
Bonus points: display the daylight as hours and minutes. Here is the relevant HTML to put in your index.html file:
<h1>
Amount of Daylight</h1>
<p>
Latitude:<input
type=
"text"
size=
"8"
id=
"latitude"
/>
°<br
/>
Enter date in format<em>
yyyy-mm-dd</em>
:<input
type=
"text"
size=
"15"
id=
"gregorian"
/><br
/>
<input
type=
"button"
value=
"Calculate"
id=
"calculate"
/>
</p>
<p>
Amount of daylight:<span
id=
"result"
></span>
</p>
See a suggested solution: Solution 3-2
Create a project named stats and write these functions, each of which takes a list of numbers as its argument:
mean
: calculates the arithmetic average of the list by summing the numbers (hint: use reduce
or apply +
) and dividing by the number of items in the list.median
: calculates the median of the numbers. The algorithm is as follows:
sort
)I used drop
in my solution rather than nth
stdev
: calculates the standard deviation of the numbers. Use the computational formula: , which works out to this algorithm.
You could write two functions, one to get the sum of the list and another to get the sum of squares (hint: use reduce
), but, as a challenge, try to write a single function to get both numbers. Hint: There is no law that says the “accumulator” of the function that you give to reduce
has to be a single number. It could just as well be a vector of two items. If you take this approach, you might want to make the reducing function a separate function rather than an anonymous function.
See a suggested solution: Solution 3-3
Now that you have the functions working, connect them to a web page where people can enter a list of numbers and the program will display the resulting statistics when the input field changes. Here’s the HTML:
<!DOCTYPE html>
<html>
<head>
<title>
Basic Statistics</title>
<meta
http-equiv=
"Content-Type"
content=
"text/html; charset=utf-8"
/>
</head>
<body>
<h1>
Basic Statistics</h1>
<p>
Enter numbers, separated by blanks or commas:<input
type=
"text"
size=
"50"
id=
"numbers"
/>
</p>
<p>
Mean:<span
id=
"mean"
></span><br
/>
Median:<span
id=
"median"
></span><br
/>
Standard deviation:<span
id=
"stdev"
></span>
</p>
<script
src=
"out/stats.js"
type=
"text/javascript"
></script>
</body>
</html>
Once you have the individual items, you have to use js/window.parseFloat
to convert them to numbers. You must do this because ClojureScript’s (and JavaScript’s) +
operator works differently on strings than on numbers: (+ "12" "30")
works out to "1230"
, not 42. Hint: use map
.
Use whichever method of interacting with JavaScript (see Interacting With JavaScript and Web Pages) that you prefer. In this étude you will listen for a change
event, and you may want to use the JavaScript event.target
property. Given a function like (defn handler [evt] ...)
, here is how you access the value of a form field via the target property:
Library | ClojureScript |
---|---|
JavaScript Google Closure | (.-value (.-target evt)) |
dommy | (dommy/value (.-target evt)) |
Domina | (domina/value (domina.events/target evt)) |
Enfocus | (ef/at (.-target evt) (ef/get-prop :value)) |
See a suggested solution: Solution 3-4
OK, I’ll admit this is a fairly strange étude, but I couldn’t resist. Dentists check the health of your gums by checking the depth of the “pockets” at six different locations around each of your 32 teeth. The depth is measured in millimeters. If any of the depths is greater than or equal to four millimeters, that tooth needs attention. (Thanks to Dr. Patricia Lee, DDS, for explaining this to me.)
Your task is to write a function named alert
that takes a vector of 32 vectors of six numbers as its input. If a tooth isn’t present, it is represented by the empty vector []
instead of the six numbers. The function produces a list of the tooth numbers that require attention. The numbers must be in ascending order.
Here’s a definition of a set of pocket depths for a person who has had her upper wisdom teeth, numbers 1 and 16, removed. Just copy and paste it into your project. Note that list entries may be separated by either a comma or by spaces.
(def pocket-depths [[], [2 2 1 2 2 1], [3 1 2 3 2 3], [3 1 3 2 1 2], [3 2 3 2 2 1], [2 3 1 2 1 1], [3 1 3 2 3 2], [3 3 2 1 3 1], [4 3 3 2 3 3], [3 1 1 3 2 2], [4 3 4 3 2 3], [2 3 1 3 2 2], [1 2 1 1 3 2], [1 2 2 3 2 3], [1 3 2 1 3 3], [], [3 2 3 1 1 2], [2 2 1 1 3 2], [2 1 1 1 1 2], [3 3 2 1 1 3], [3 1 3 2 3 2], [3 3 1 2 3 3], [1 2 2 3 3 3], [2 2 3 2 3 3], [2 2 2 4 3 4], [3 4 3 3 3 4], [1 1 2 3 1 2], [2 2 3 2 1 3], [3 4 2 4 4 3], [3 3 2 1 2 3], [2 2 2 2 3 3], [3 2 3 2 3 2]])
And here’s the output:
cljs.user=> (in-ns 'teeth.core) nil teeth.core=> (alert pocket-depths) [9 11 25 26 29] teeth.core=>
See a suggested solution: Solution 3-5
How do you think I got the numbers for the teeth in the preceding étude? Do you really think I made up and typed all 180 of them? No, of course not. Instead, I wrote a ClojureScript program to create the vector of vectors for me, and that's what you'll do in this étude.
ClojureScript is luckily provided with the rand
function. It generates a random floating point number from 0 up to but not including 1 (if given no argument), or, if given a single argument n, returns a random floating value from 0 up to n. More useful for this étude is the rand-int
function, which takes one argument n and returns a random integer from 0 up to but not including n.
Create a project named make_teeth
and write a function
generate-pockets
that takes two arguments. The first argument is a string consisting of the letters T
and F
. A T
indicates that the tooth is present, and an F
indicates a missing tooth. The second argument is a floating point number between 0 and 1.0 (inclusive) that indicates the probability that a tooth will be a good tooth.
The result is a vector of vectors, one sub-vector per tooth. If a tooth is present, the sub-vector has six entries; if a tooth is absent, the sublist is empty: []
. Here is some sample output from the REPL.
make_teeth.core=> (generate-pockets "TFTT" 0.75) [[1 2 2 3 1 1] [] [2 3 1 1 3 2] [4 2 2 3 2 3]]
These are the helper functions I needed:
(generate-list teeth-present probability result)
generate_pockets
; the
third argument is the accumulated list. If a tooth isn’t present, add []
to the result; otherwise add the return value of generate_tooth
with the probability of a good tooth as its argument.
(one-tooth present probability)
"T"
or "F"
) to signiify the presence or absence of a tooth, and the probability of a good tooth. If there’s no tooth, it returns []
. Otherwise, it sets a “base depth” for all the pockets by generating a random number between 0 and 1. If that number is less than the probability
of a good tooth, base depth is 2, otherwise it’s 3. It then generates a vector of six numbers, each time randomly adding an integer from -1 to 1 to the base depth.
I imagine that, with a great deal of effort, I could have found a way to use map
and reduce
to give me the results I wanted, but I was too lazy. Instead, I used recur
in generate-list
and loop
/recur
in one-tooth
.
See a suggested solution: Solution 3-6
This étude puts together a lot of the things you have been doing in this chapter into one rather large-ish project. The project name is daylight_summary, and it gives a table of average minutes of daylight per month for a given latitude or city (selected from a drop-down menu). Here is the HTML:
<!DOCTYPE html>
<html>
<head>
<title>
Amount of Daylight</title>
<meta
http-equiv=
"Content-Type"
content=
"text/html; charset=utf-8"
/>
<style
type=
"text/css"
>
th
,
td
{
border
:
1px
solid
gray
;
padding
:
0.5em
;
}
</style>
</head>
<body>
<h1>
Amount of Daylight</h1>
<p>
<input
type=
"radio"
name=
"locationType"
id=
"menu"
checked=
"checked"
>
<select
id=
"cityMenu"
>
<option
value=
"39.9075"
>
Beijing</option>
<option
value=
"52.52437"
>
Berlin</option>
<option
value=
"-15.77972"
>
Brasília</option>
<option
value=
"30.06263"
>
Cairo</option>
<option
value=
"-35.28346"
>
Canberra</option>
<option
value=
"-17.82772"
>
Harare</option>
<option
value=
"-12.04318"
>
Lima</option>
<option
value=
"51.50853"
>
London</option>
<option
value=
"55.75222"
>
Moscow</option>
<option
value=
"-1.28333"
>
Nairobi</option>
<option
value=
"28.63576"
>
New Delhi</option>
<option
value=
"12.36566"
>
Ouagadougou</option>
<option
value=
"59.91273"
>
Oslo</option>
<option
value=
"48.85341"
>
Paris</option>
<option
value=
"35.6895"
>
Tokyo</option>
<option
value=
"38.89511"
>
Washington, D. C.</option>
</select>
<input
type=
"radio"
id=
"userSpecified"
name=
"locationType"
>
Other latitude:<input
type=
"text"
size=
"8"
id=
"latitude"
/>
<input
type=
"button"
value=
"Calculate"
id=
"calculate"
/>
</p>
<h2>
Monthly Average Daylight</h2>
<table>
<thead><tr><th>
Month</th><th>
Average</th></tr></thead>
<tbody>
<tr><td>
January</td><td
id=
"m1"
></td></tr>
<tr><td>
February</td><td
id=
"m2"
></td></tr>
<tr><td>
March</td><td
id=
"m3"
></td></tr>
<tr><td>
April</td><td
id=
"m4"
></td></tr>
<tr><td>
May</td><td
id=
"m5"
></td></tr>
<tr><td>
June</td><td
id=
"m6"
></td></tr>
<tr><td>
July</td><td
id=
"m7"
></td></tr>
<tr><td>
August</td><td
id=
"m8"
></td></tr>
<tr><td>
September</td><td
id=
"m9"
></td></tr>
<tr><td>
October</td><td
id=
"m10"
></td></tr>
<tr><td>
November</td><td
id=
"m11"
></td></tr>
<tr><td>
December</td><td
id=
"m12"
></td></tr>
</tbody>
</table>
<script
src=
"out/daylight_summary.js"
type=
"text/javascript"
></script>
</body>
</html>
In this program, don’t worry about leap years; do the calculation based on a 365-day year.
To determine which of the radio buttons is selected, you use code like this in Enfocus, where ef
is the abbreviation for the enfocus.core
namespace:
(
ef/from
"input[name='locationType']"
(
ef/get-prop
:checked
)))
The selector is a CSS style selector, and the expression returns a list of the status of the two radio buttons, with true
if selected and false
if not.
If you are using Domina, use code like this, again using a CSS selector:
(
def
radio
(
domina/nodes
(
domina.css/sel
"input[name='locationType']"
)))
(
domina/value
(
first
radio
))
The result of the second expression is the string "on"
if the radio button is selected, nil
if not.
See a suggested solution: Solution 3-7