From 90df73f67a5071aaad16201d1075aad38d48ca56 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Tue, 25 Apr 2023 01:29:02 -0400 Subject: [PATCH 01/12] Added example solutions --- doc/scripts/hashsets.clj | 90 ++++++++++++++++++++++++++++++++++++---- doc/scripts/trees.clj | 76 +++++++++++++++++++++++++++++---- doc/scripts/vectors.clj | 22 +++++++++- 3 files changed, 173 insertions(+), 15 deletions(-) diff --git a/doc/scripts/hashsets.clj b/doc/scripts/hashsets.clj index 3d209a9..d153c74 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 ef65af9..3beead5 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))))))) (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))))))) ;; 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 From 5b677a3ada00418ba1e9fc29c5a34fcbbf2fd4f6 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Tue, 25 Apr 2023 01:29:02 -0400 Subject: [PATCH 02/12] Added example solutions --- doc/scripts/hashsets.clj | 90 ++++++++++++++++++++++++++++++++++++---- doc/scripts/trees.clj | 76 +++++++++++++++++++++++++++++---- doc/scripts/vectors.clj | 22 +++++++++- 3 files changed, 173 insertions(+), 15 deletions(-) diff --git a/doc/scripts/hashsets.clj b/doc/scripts/hashsets.clj index 3d209a9..d153c74 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 6f969bb..7a2537e 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))))))) (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))))))) ;; 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 From 7df5ceb6b363756e09d48bf6f79426d3f91ac017 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:01:09 -0400 Subject: [PATCH 03/12] added missing param --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index 7a2537e..a9444fe 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -227,7 +227,7 @@ ;; 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))))))) + (recur (if (< value v) (left n) (right node))) value)))) (def t (utree [5 1 9])) (tcontains? t 1) From 70ca7cafb6bd72e8b00854d4ac92f21cdea2a015 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:22:00 -0400 Subject: [PATCH 04/12] added missing parameter --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index a9444fe..a537f69 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -441,7 +441,7 @@ ;; 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))))))) + (recur (if (< c 0) (left node) (right node)) key))))) ;; The following map of labels to numbers can be used to demonstrate this function: From 99c51527702607ee13e57a8e467d4119ded62058 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:24:31 -0400 Subject: [PATCH 05/12] Updated comment on Java HashSets --- doc/scripts/trees.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index a537f69..32b73a0 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -470,8 +470,9 @@ (tkeys numbers) (tvals numbers) -;; Remember that in the Java memory model, the string does not appear twice in the array. Instead, -;; _reference_ to the string is stored twice. This map of objects to themselves is the approach -;; that Java uses for implementing Hashsets. +;; Another approach to sets is to use maps and associate keys to themselves. This makes both the +;; key and the value the same thing. Remember that in the Java memory model, the string does not +;; appear twice in the array. Instead, a _reference_ to the string is stored twice, which is less +;; expensive.. This map of objects to themselves is the approach that Java uses for implementing Hashsets. ;; Now that we know about immutable trees, we can build Vectors From 833f7e9a291adc715ed08df9dfb35b19c38345cf Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:27:51 -0400 Subject: [PATCH 06/12] Removed extra period --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index 32b73a0..1a27d12 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -473,6 +473,6 @@ ;; Another approach to sets is to use maps and associate keys to themselves. This makes both the ;; key and the value the same thing. Remember that in the Java memory model, the string does not ;; appear twice in the array. Instead, a _reference_ to the string is stored twice, which is less -;; expensive.. This map of objects to themselves is the approach that Java uses for implementing Hashsets. +;; expensive. This map of objects to themselves is the approach that Java uses for implementing Hashsets. ;; Now that we know about immutable trees, we can build Vectors From f99d5d525aaf93692616b789ea0ef9e33a4c7cc3 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Tue, 25 Apr 2023 01:29:02 -0400 Subject: [PATCH 07/12] Added example solutions --- doc/scripts/hashsets.clj | 90 ++++++++++++++++++++++++++++++++++++---- doc/scripts/trees.clj | 76 +++++++++++++++++++++++++++++---- doc/scripts/vectors.clj | 22 +++++++++- 3 files changed, 173 insertions(+), 15 deletions(-) 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..986a761 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))))))) (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))))))) ;; 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 From 39870c018824f6f61debd3ec10d5f9495e921616 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:01:09 -0400 Subject: [PATCH 08/12] added missing param --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index 986a761..db56e51 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -227,7 +227,7 @@ ;; 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))))))) + (recur (if (< value v) (left n) (right node))) value)))) (def t (utree [5 1 9])) (tcontains? t 1) From 72586375a6a02fa23a5b57b108c75f3ae438e073 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:22:00 -0400 Subject: [PATCH 09/12] added missing parameter --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index db56e51..1a27d12 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -441,7 +441,7 @@ ;; 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))))))) + (recur (if (< c 0) (left node) (right node)) key))))) ;; The following map of labels to numbers can be used to demonstrate this function: From 754e4410010c819c58cb153cea76fd9344e4890d Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Tue, 25 Apr 2023 01:29:02 -0400 Subject: [PATCH 10/12] Added example solutions --- doc/scripts/hashsets.clj | 90 ++++++++++++++++++++++++++++++++++++---- doc/scripts/trees.clj | 76 +++++++++++++++++++++++++++++---- doc/scripts/vectors.clj | 22 +++++++++- 3 files changed, 173 insertions(+), 15 deletions(-) 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..986a761 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))))))) (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))))))) ;; 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 From 60d310113727a4c21408cd00c2fbb2bd83f50db7 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:01:09 -0400 Subject: [PATCH 11/12] added missing param --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index 986a761..db56e51 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -227,7 +227,7 @@ ;; 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))))))) + (recur (if (< value v) (left n) (right node))) value)))) (def t (utree [5 1 9])) (tcontains? t 1) From 773065c11175ab974e73258e247710ade0e50d42 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Wed, 26 Apr 2023 11:22:00 -0400 Subject: [PATCH 12/12] added missing parameter --- doc/scripts/trees.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts/trees.clj b/doc/scripts/trees.clj index db56e51..1a27d12 100644 --- a/doc/scripts/trees.clj +++ b/doc/scripts/trees.clj @@ -441,7 +441,7 @@ ;; 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))))))) + (recur (if (< c 0) (left node) (right node)) key))))) ;; The following map of labels to numbers can be used to demonstrate this function: