CSC 533: Programming Languages
Spring 2024

HW5: Clojure Structures

Note: late submissions will not be accepted for this assignment.


For this assignment, you will modify and add to functions that are provided for you in hw5.clj. Download this file, rename it LASTNAME-hw5.clj and enter your name in the comment at the top. Be careful to name your functions exactly as defined in the exercises, and be sure to comment each function as to its behavior.

PART 1: Molecular Weights

The periodic table, which identifies each element and its atomic weight, is listed below as a Clojure map: (def PERIODIC-TABLE {:H [:Hydrogen 1.0080] :He [:Helium 4.0026] :Li [:Lithium 6.9410] :Be [:Beryllium 9.0122] :B [:Boron 10.8100] :C [:Carbon 12.0110] :N [:Nitrogen 14.0067] :O [:Oxygen 15.9994] . . . :Ts [:Tennessine 294.0000] :Og [:Oganesson 294.0000]})

This map is defined in the downloaded hw5.clj file. Add the following definitions to the file.

  1. Define a function named atomic-number that takes one input, the keyword for an element, and returns the atomic number of that element (i.e., its position in the table). For example, (atomic-number :H) should evaluate to 1 while (atomic-number :O) should evaluate to 8.
  2. Define a function named atomic-weight that takes one input, the keyword for an element, and returns the atomic weight of that element. For example, (atomic-weight :H) should evaluate to 1.008 while (atomic-weight :O) should evaluate to 15.9994.
  3. Define a function named molecular-weight1 that takes one input, a list of element keywords, and returns the molecular weight (i.e., the sum of all of the atomic weights) for that list. For example, (molecular-weight1 '(:H :H :O)) should evaluate to 18.0154.
  4. When writing formulas for molecules in which an element appears multiple times, scientists use subscripts to denote repetition. For example, the formula for water is written H2O instead of HHO. Copy your molecular-weight1, rename it molecular-weight2, and modify it to handle lists in which an element may be followed by a number. For example, (molecular-weight2 '(:H 2 :O)) should evaluate to 18.0154.
  5. Complex formulas can contain nested sub-formulas. For example, the formula for isopropyl alcohol is (CH3)2CHOH. Copy your molecular-weight2, rename it molecular-weight3, and modify it to handle nested lists. For example, (molecular-weight3 '((:C :H 3) 2 :C :H :O :H)) should evaluate to 60.0964.

PART 2: Binary Trees

In class, we discussed how structured lists could be used to represent binary trees. For example, the following lists represent a tree of animal names (strings) and a tree of numbers, respectively.

(def ANIMALS '(:dog (:bird (:horse () ()) (:cat () ())) (:possum (:dog () ()) ()))) (def NUMBERS '(2 (-1 () ()) (3 () ())))

Several utility functions (root, left-subtree, and right-subtree) are provided for you in hw5.clj. Add the following functions for manipulating arbitrary binary trees.

  1. Define a function named count-keywords that takes one input, a list representing a binary tree, and returns the number of keywords that are stored in the tree. For example, (count-keywords ANIMALS) should evaluate to 6.
  2. Define a function named count-func that generalizes the count-keywords function by adding a second input: a predicate function (i.e., one that returns true/false). The count-func function should return a count of how many items in the tree evaluate to true using the predicate function. For example, (count-func keyword? ANIMALS) should behave the same as count-keywords from the previous exercise. Similarly, (count-func pos? NUMBERS) should return 2.
  3. Recall that the map function takes a function and maps it to a list of values. For example, (map inc '(1 3 5)) would evaluate to (2 4 6). Define a a function named tree-map that similar maps a function to a tree structure. For example, the call (tree-map inc NUMBERS) would produce the tree (3 (0 () ()) (4 () ())).

PART 3: OOP in Clojure

In class, we discussed how Clojure allows for the definition of classes via protocols and type definitions. For example, consider the following definitions for implementing a Coin class (also in hw5.clj).

(defprotocol CoinInterface (flip [this]) (num-flips [this])) (deftype Coin [options ^:unsynchronized-mutable flips] CoinInterface (flip [this] (do (set! flips (inc flips)) (if (< (rand) 0.5) (first options) (second options)))) (num-flips [this] flips)) (defn make-coin ([] (Coin. [:HEADS, :TAILS] 0)) ([options] (Coin. options 0)))

For example:

(def penny (make-coin)) => #'functional.core/penny (flip penny) => :TAILS (flip penny) => :HEADS (flip penny) => :TAILS (num-flips penny) => 3
  1. Redefine the count-heads function from the lectures to utilize the above "class." Recall that this function takes one input, the number of flips, and returns the number of heads obtained. For example, (count-heads 1000) should return a number close to 500. As before, this function must utilize tail-recursion. Note: a single coin object should be created and used appropriately for the repeated flips.
  2. Define a function named flip-until-heads that simulates repeated flips of a coin until heads is obtained, prints the value of each flip, and returns the number of flips. For example, (flip-until-heads) might produce the following:
  3. (flip-until-heads) :TAILS :TAILS :HEADS => 3