From 02b4ee1945006dbbc538af8e81d87a9838fba67f Mon Sep 17 00:00:00 2001 From: fr33m0nk Date: Thu, 4 Apr 2024 09:37:17 +0530 Subject: [PATCH] Add Dynamic array implementation --- DIRECTORY.md | 2 + src/data_structures/dynamic_array/core.clj | 85 +++++++++++ .../dynamic_array/core_test.clj | 132 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 src/data_structures/dynamic_array/core.clj create mode 100644 test/data_structures/dynamic_array/core_test.clj diff --git a/DIRECTORY.md b/DIRECTORY.md index 2ffe1c7..5a45774 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -16,6 +16,8 @@ * [Sublist of a list by consecutive repetitions](https://github.com/TheAlgorithms/Clojure/blob/main/src/data_structures/lists/pack_consecutive_repetitions_in_sublist.clj) * [Run length encoding](https://github.com/TheAlgorithms/Clojure/blob/main/src/data_structures/lists/run_length_encoding.clj) +* [Dynamic Array implementation](/src/data_structures/dynamic_array/core.clj) + ## Sorting * [Merge Sort](https://github.com/TheAlgorithms/Clojure/blob/main/src/sorts/merge_sort.clj) * [Quick Sort](https://github.com/TheAlgorithms/Clojure/blob/main/src/sorts/quick_sort.clj) diff --git a/src/data_structures/dynamic_array/core.clj b/src/data_structures/dynamic_array/core.clj new file mode 100644 index 0000000..14f8535 --- /dev/null +++ b/src/data_structures/dynamic_array/core.clj @@ -0,0 +1,85 @@ +(ns data-structures.dynamic-array.core + (:refer-clojure :exclude [get set empty? remove pop])) + +;; See tests for the documentation on implementation +(defprotocol IDynamicArray + (get [this idx]) + (set [this idx value]) + (remove [this idx]) + (empty? [this]) + (length [this]) + (next-idx [this]) + (append [this value]) + (pop [this])) + +(deftype DynamicArray [^:unsynchronized-mutable length + ^:unsynchronized-mutable next-idx + ^:unsynchronized-mutable array] + IDynamicArray + (get [_ idx] + (assert (< -1 idx next-idx) "Invalid index value supplied") + (aget array idx)) + (set [_ idx value] + (assert (<= 0 idx) "Invalid index value supplied") + (when (or (= next-idx length) (> idx length)) + (let [next-length (* idx 2) + next-array (make-array Integer/TYPE next-length)] + (doseq [i (range length)] + (->> (aget array i) + (aset next-array i))) + (set! array next-array) + (set! length next-length))) + (aset array next-idx value) + (set! next-idx (inc idx)) + idx) + (remove [_ idx] + (assert (< -1 idx next-idx) "Invalid index value supplied") + (let [next-next-idx (dec next-idx) + popped-element (aget array idx)] + (doseq [dest-idx (range idx next-idx) + :let [source-idx (inc dest-idx)] + :when (not= source-idx length)] + (aset array dest-idx (aget array source-idx))) + (set! next-idx next-next-idx) + popped-element)) + (empty? [_] + (zero? next-idx)) + (length [_] + (alength array)) + (next-idx [this] + next-idx) + (append [_ value] + (when (= next-idx length) + (let [next-length (* length 2) + next-array (make-array Integer/TYPE next-length)] + (doseq [i (range length)] + (->> (aget array i) + (aset next-array i))) + (set! array next-array) + (set! length next-length))) + (let [old-capacity next-idx] + (aset array next-idx value) + (set! next-idx (inc next-idx)) + old-capacity)) + (pop [_] + (assert (> next-idx 0) "Nothing to pop") + (let [next-next-idx (dec next-idx) + popped-element (aget array next-next-idx)] + (aset array next-next-idx 0) + (set! next-idx next-next-idx) + popped-element)) + Object + (toString [_] + (let [^StringBuilder sb (StringBuilder.)] + (.append sb "[ ") + (doseq [i (range next-idx)] + (.append sb (aget array i)) + (.append sb " ")) + (.append sb "]") + (.toString sb)))) + +(defn ->DynamicArray + [initial-capacity] + (when (>= 0 initial-capacity) + (throw (IllegalArgumentException. "Invalid initial capacity"))) + (DynamicArray. initial-capacity 0 (make-array Integer/TYPE initial-capacity))) diff --git a/test/data_structures/dynamic_array/core_test.clj b/test/data_structures/dynamic_array/core_test.clj new file mode 100644 index 0000000..cb559bb --- /dev/null +++ b/test/data_structures/dynamic_array/core_test.clj @@ -0,0 +1,132 @@ +(ns data-structures.dynamic-array.core-test + (:require [clojure.test :refer [deftest testing is]] + [data-structures.dynamic-array.core :as da])) + +(deftest ->DynamicArray-test + (testing "throws when invalid initial capacity is provided" + (is (thrown? IllegalArgumentException (da/->DynamicArray 0))) + (is (thrown? IllegalArgumentException (da/->DynamicArray -1))))) +(deftest empty?-test + (testing "return true if dynamic array is empty" + (let [dynamic-array (da/->DynamicArray 3)] + (is (true? (da/empty? dynamic-array))))) + + (testing "return false if dynamic array is empty" + (let [dynamic-array (da/->DynamicArray 3)] + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (is (false? (da/empty? dynamic-array)))))) + +(deftest length-test + (testing "returns initial capacity till dynamic array is full" + (let [dynamic-array (da/->DynamicArray 3)] + (is (= 3 (da/length dynamic-array))))) + + (testing "returns expanded capacity once storage demands exceeds initial capacity" + (let [dynamic-array (da/->DynamicArray 3)] + (da/set dynamic-array (da/next-idx dynamic-array) 10) + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (da/set dynamic-array (da/next-idx dynamic-array) 40) + (is (= 6 (da/length dynamic-array)))))) + +(deftest next-idx-test + (testing "returns initial index as the starting point when dynamic array is empty" + (let [dynamic-array (da/->DynamicArray 3)] + (is (= 0 (da/next-idx dynamic-array))))) + + (testing "returns current filled index as the starting point when dynamic array is empty" + (let [dynamic-array (da/->DynamicArray 3)] + (da/set dynamic-array (da/next-idx dynamic-array) 10) + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (is (= 3 (da/next-idx dynamic-array)))))) + +(deftest set-test + (let [dynamic-array (da/->DynamicArray 3)] + (testing "throws Assertion error when index is less than 0" + (is (thrown? AssertionError (da/set dynamic-array -1 10)))) + + (testing "stores the element within the dynamic array when a valid index is provided" + (da/set dynamic-array (da/next-idx dynamic-array) 10) + (is (false? (da/empty? dynamic-array))) + (is (= 1 (da/next-idx dynamic-array)))) + + (testing "expands dynamic array to incorporate more elements" + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (da/set dynamic-array (da/next-idx dynamic-array) 40) + (is (= 4 (da/next-idx dynamic-array))) + (is (= 6 (da/length dynamic-array)))) + + (testing "expands dynamic array to incorporate for an arbitrary large index and sets next-idx accordingly" + (da/set dynamic-array 60 40) + (is (= 61 (da/next-idx dynamic-array)) "This behaviour causes fragmentation in the dynamic array") + (is (= 120 (da/length dynamic-array)))))) + +(deftest get-test + (let [dynamic-array (da/->DynamicArray 3)] + (testing "throws Assertion error when index is less than 0" + (is (thrown? AssertionError (da/get dynamic-array -1)))) + + (testing "throws Assertion error when index is greater than next-index" + (is (thrown? AssertionError (da/get dynamic-array (inc (da/next-idx dynamic-array)))))) + + (testing "fetches the content of the dynamic array stored at valid index" + (da/set dynamic-array (da/next-idx dynamic-array) 40) + (is (= 40 (da/get dynamic-array (dec (da/next-idx dynamic-array)))))))) + +(deftest remove-test + (let [dynamic-array (da/->DynamicArray 3)] + (testing "throws Assertion error when index is less than 0" + (is (thrown? AssertionError (da/remove dynamic-array -1)))) + + (testing "throws Assertion error when index is greater than next index" + (is (thrown? AssertionError (da/remove dynamic-array (inc (da/next-idx dynamic-array)))))) + + (testing "removes the element from the dynamic array and returns it if the index is valid" + (da/set dynamic-array (da/next-idx dynamic-array) 10) + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (da/set dynamic-array (da/next-idx dynamic-array) 40) + (is (= 4 (da/next-idx dynamic-array)) "Sanity check to ensure that next-index is correctly calculated") + (is (= 30 (da/remove dynamic-array 2))) + (is (= 3 (da/next-idx dynamic-array))) + (is (= "[ 10 20 40 ]" (.toString dynamic-array)))))) + +(deftest append-test + (let [dynamic-array (da/->DynamicArray 3)] + (testing "appends values to dynamic array" + (da/set dynamic-array (da/next-idx dynamic-array) 10) + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (is (= 2 (da/next-idx dynamic-array))) + (is (= 3 (da/length dynamic-array)))) + + (testing "expands dynamic array to allocate more elements" + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (da/set dynamic-array (da/next-idx dynamic-array) 40) + (is (= 4 (da/next-idx dynamic-array))) + (is (= 6 (da/length dynamic-array)))))) + +(deftest pop-test + (let [dynamic-array (da/->DynamicArray 3)] + (testing "throws when the dynamic array is empty" + (is (thrown? AssertionError (da/pop dynamic-array)))) + + (testing "pops the element from dynamic array and returns it" + (da/set dynamic-array (da/next-idx dynamic-array) 10) + (da/set dynamic-array (da/next-idx dynamic-array) 20) + (da/set dynamic-array (da/next-idx dynamic-array) 30) + (is (= 30 (da/pop dynamic-array))) + (is (= 2 (da/next-idx dynamic-array)))))) + +(deftest toString-test + (testing "provides string representation of an empty array" + (let [dynamic-array (da/->DynamicArray 3)] + (is (= "[ ]" (.toString dynamic-array))))) + + (testing "provides string representation of a non empty array" + (let [dynamic-array (da/->DynamicArray 10)] + (doseq [i (range 10 15)] + (da/append dynamic-array i)) + (is (= "[ 10 11 12 13 14 ]" (.toString dynamic-array))))))