diff --git a/doc/scripts/hashsets.clj b/doc/scripts/hashsets.clj index dfb9825..717138e 100644 --- a/doc/scripts/hashsets.clj +++ b/doc/scripts/hashsets.clj @@ -124,7 +124,22 @@ words ;; #### Exercise ;; Rewrite `insert!` and `contains-object?` to use a linked list at each array location -;; _Solution goes here_ +;; A Solution: +(defn insert! + [arr s] + (let [pos (array-location (count arr) s) + existing-list (nth arr pos)] + (if (some #(= s %) existing-list) ;; search the list + arr ;; already there, so just return the original array + (aset arr pos (cons s existing-list))))) ;; add to the list, and put this back in the array + +(defn contains-object? + [arr s] + ;; find the list at the location in the array + (let [found (nth arr (array-location (count arr) s))] + ;; search the list + (some #(= s %) found))) + ;; Let's test this by repopulate the array of words, and adding "apple": (def words (object-array 10)) @@ -242,8 +257,10 @@ words50 ;; #### Exercise ;; Rewrite `array-location` to use the `hash` function - -;; _Solution goes here_ +;; A Solution: +(defn array-location + [size s] + (mod (hash s) size)) ;; When using a REPL, the `insert` and `contains-object?` functions will pick up this new version of `array-location` ;; (defn insert! @@ -293,7 +310,29 @@ words50 (aset p 1 v) p)) -;; _Solution goes here_ +;; A Solution: +(defn insert-map! + [arr k v] + ;; get the location in the array + (let [pos (array-location (count arr) k) + ;; get the existing data at that location + existing (nth arr pos)] + ;; check the first of each pair, to see if it matches the key + (if-let [[fk fv] (first (filter #(= k (first %)) existing))] + ;; see if the value is changing + (if (= fv v) + arr ;; no change + ;; otherwise, remove the old pair, and add the new one + (aset arr pos (cons (pair k v) (remove #(= k (first %)) existing)))) + ;; the key is not there, so just add the pair + (aset arr pos (cons (pair k v) existing))))) + +(defn get-map + [arr k] + ;; find the existing list of pairs + (let [found (nth arr (array-location (count arr) k))] + ;; search the list for the matching key, then return the value + (second (first (filter #(= k (first %)) found))))) ;; Let's test this out with an array of size 50: (def map-array (object-array 50)) @@ -321,7 +360,10 @@ map-array ;; Write a `to-seq` function that will convert the hashmap array to a seq of pairs. ;; Convert these pairs to 2 element vectors. -;; _Solution goes here_ +;; A Solution: +(defn to-seq + [arr] + (mapcat #(map vec %) arr)) ;; Apply this to the map-array: (to-seq map-array) @@ -330,7 +372,16 @@ map-array ;; Write a `map-keys` function and a `map-vals` function to duplicate the ;; `keys` and `vals` functions for maps: -;; _Solution goes here_ +;; A Solution: +(defn map-keys + [arr] + (map first (to-seq arr))) + +(defn map-vals + [arr] + (map second (to-seq arr))) + +;; Not all of the exercises have to be hard! ;; ## Immutability ;; In order to follow how to make these structures immutable, let's turn to Tree structures. @@ -345,7 +396,32 @@ map-array ;; Write the functions `insert` and `get-map` to duplicate the previous functionality, but ;; now the hashmap is in a vector, and the operations must be immutable. -;; _Solution goes here_ +;; A solution. This includes all operations: + +(defn location + [size s] + (mod (hash s) size)) + +(defn insert + ;; accept a key/value as a pair + ([vctr [k v]] (insert vctr k v)) + ;; accept the key and value as separate arguments + ([vctr k v] + (let [pos (location (count vctr) k) + existing (nth vctr pos)] + (if-let [[fk fv] (first (filter #(= k (first %)) existing))] + (if (= fv v) + vctr + (assoc vctr pos (cons [k v] (remove #(= k (first %)) existing)))) + (assoc vctr pos (cons [k v] existing)))))) + +(defn get-map + [vctr k] + (let [found (nth vctr (location (count vctr) k))] + (second (first (filter #(= k (first %)) found))))) + +;; Neither the `location` not the `get-map` function needed to change. +;; only the `insert` function changed, to use `assoc` rather than `aset` ;; Initialize a vector at the full width, full of nils: (def vctr (vec (repeat 50 nil))) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index d492a5a..1a27d12 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -93,7 +93,30 @@ [[5, [1, nil, [3, nil, nil]], [9, nil, nil]]] -;; _Solution goes here_ + +;; A Solution: +(defn put! + [tree value] + (if-not (root tree) + ;; if empty, create a new tree with just one node + (set-root! tree (node value)) + ;; iterate down the branches + (loop [current-node (root tree)] + ;; read the number at the current node + (let [current-value (data current-node)] + (if (= current-value value) + ;; when equal to the number being inserted, then do nothing and return. + tree + ;; determine the side. Smaller goes to the left, larger to the right. + (let [side (if (< value current-value) :left :right)] + (if-let [child-node (child current-node side)] + ;; if a child node exists on that side, step down and repeat + (recur child-node) + ;; no child on the selected side, so create a leaf node and insert it + (do + (set-child! current-node side (node value)) + ;; return the root of the tree + tree)))))))) ;; Try it out... @@ -114,7 +137,17 @@ ;; Calling `(t-seq t)` should return `(1 3 5 9)` -;; _Solution goes here_ +;; A Solution: +(defn as-seq + [node] + (and node + (concat (as-seq (left node)) + [(data node)] + (as-seq (right node))))) + +(defn t-seq + [tree] + (as-seq (root tree))) (t-seq t) @@ -177,13 +210,24 @@ ;; #### Exercise: ;; Write a function `utree` that creates a tree from a seq parameter. -;; _Solution goes here_ +;; A solution: +(defn utree [s] (reduce put nil s)) ;; #### Exercise: ;; Write a function called `tcontains?` which checks if a value has been stored in the tree. This is the same operation ;; as `clojure.core/contains?` -;; _Solution goes here_ +;; A solution: +(defn tcontains? + [node value] + (and node ;; test if the tree rooted at `node` is not empty + ;; look at the data for the current node + (let [v (data node)] + (or + ;; if the data node data is the same as what is being looked for, then return true + (= value v) + ;; otherwise, recurse into the left child branch if the value is smaller, or the right if larger + (recur (if (< value v) (left n) (right node))) value)))) (def t (utree [5 1 9])) (tcontains? t 1) @@ -315,7 +359,12 @@ ;; #### Exercise: ;; Write a `max-depth` function to return the maximum depth of the rb-tree. Hint: use recursion. -;; _Solution goes here_ +;; A Solution: +(defn max-depth + [n] + (if n + (inc (max (max-depth (left n)) (max-depth (right n)))) + 0)) ;; The maximum depth can indicate that a tree is relatively balanced. A perfectly balanced tree ;; ought to be about the log2 of the number of items. Red/Black trees do not balance perfectly, but we should @@ -381,7 +430,18 @@ ;; Write `tget`: a function that adds to the `tcontains?` function to search for a given key, ;; returning the associated value. -;; _Solution goes here_ +;; A solution: +(defn tget + [node key] + ;; return nil for an empty tree + (and node + ;; compare the value at this node to the key being searched for + (let [c (compare key (nkey node))] + (if (zero? c) + ;; when the node contains the required key, return the associated value + (value node) + ;; key not at this node, so look in the children, going left for smaller, right for larger. + (recur (if (< c 0) (left node) (right node)) key))))) ;; The following map of labels to numbers can be used to demonstrate this function: @@ -403,7 +463,9 @@ ;; Implement the `tkeys` and `tvals` functions that return the keys and values of this tree set as seqs. ;; Hint: you can use the `as-seq` from above. -;; _Solution goes here_ +;; A Solution: +(defn tkeys [tree] (map first (as-seq tree))) +(defn tvals [tree] (map second (as-seq tree))) (tkeys numbers) (tvals numbers) diff --git a/doc/scripts/vectors.clj b/doc/scripts/vectors.clj index 5b0691c..5c7b29f 100644 --- a/doc/scripts/vectors.clj +++ b/doc/scripts/vectors.clj @@ -152,7 +152,27 @@ v ;; Write a function called vth that can manually retrieve the nth element from a vector that contains ;; between 1057 and 32800 elements. i.e. There are 3 levels. -;; _Solution goes here_ +;; A Solution: +(defn vth + [v n] + ;; check that the offset is not out of range + (when (< n (count v)) + ;; get the offset in the root + (let [first-level (int (/ n 1024)) + ;; record the remaining offset + n2 (mod n 1024) + ;; the offset in the second level of the tree + second-level (int (/ n2 32)) + ;; the final offset + third-level (mod n2 32)] + ;; check if the offset is beyond the range of the first level + (if (or (> first-level 32) + ;; or the second level offset is a nil node + (nil? (-> v .root .array (nth first-level) .array (nth second-level)))) + ;; this indicates that the offset is beyond the current data and inside the tail + (-> v .tail (nth third-level)) + ;; otherwise, traverse down the tree to the offset + (-> v .root .array (nth first-level) .array (nth second-level) .array (nth third-level)))))) (vth vec1057 500) ;; 500