From fcd6263d3caf51ccbe039d7431e4a67c8e7f3e4e Mon Sep 17 00:00:00 2001 From: Denis Smetannikov Date: Wed, 31 Jan 2024 18:13:04 +0400 Subject: [PATCH] Add exercise 2.69 (#145) * Add exercise 2.69 Implemented exercise 2.69 which involves generating a Huffman encoding tree. Updated the make-leaf-set function in the book_2_3.clj file for correct functionality. The README has been updated to reflect the progress of completed exercises and the addition of exercise 2.69. * Refactor Huffman Tree related tests and logic Refactored the logic related to Huffman Tree examples and tests. Created constants for symbols, messages, and Huffman Tree in book_2_3.clj and reference these constants in tests to reduce code duplication. This also allows easier changes in future as only the main definition needs to be adjusted. * Remove unused import and update encode call in test Removed unused 'huffman-pairs' import from book_2_3_test.clj. Also, updated the call to 'encode' in ex_2_68_test.clj to use the 'huffman-tree' defined in 'b23', providing cleaner and more maintainable code. --- README.md | 6 +-- src/sicp/chapter_2/part_3/book_2_3.clj | 39 +++++++++++++++++++- src/sicp/chapter_2/part_3/ex_2_69.clj | 35 ++++++++++++++++++ test/sicp/chapter_2/part_3/book_2_3_test.clj | 11 ++++-- test/sicp/chapter_2/part_3/ex_2_67_test.clj | 16 +------- test/sicp/chapter_2/part_3/ex_2_68_test.clj | 23 ++++-------- test/sicp/chapter_2/part_3/ex_2_69_test.clj | 14 +++++++ 7 files changed, 106 insertions(+), 38 deletions(-) create mode 100644 src/sicp/chapter_2/part_3/ex_2_69.clj create mode 100644 test/sicp/chapter_2/part_3/ex_2_69_test.clj diff --git a/README.md b/README.md index dcf0719..e19c9c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Solving exercises from SICP with Clojure [![Clojure CI](https://github.com/SmetDenis/Clojure-Sicp/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/SmetDenis/Clojure-Sicp/actions/workflows/main.yml) -![Progress](https://progress-bar.dev/114/?scale=356&title=Solved&width=500&suffix=) +![Progress](https://progress-bar.dev/115/?scale=356&title=Solved&width=500&suffix=) SICP (Structure and Interpretation of Computer Programs) is the book of Harold Abelson and Gerald Jay Sussman on basics of computer science and software engineering. @@ -42,7 +42,7 @@ Jay Sussman on basics of computer science and software engineering. ### Chapter 2 - Building Abstractions with Data -![Progress](https://progress-bar.dev/68/?scale=97&title=Solved&width=500&suffix=) +![Progress](https://progress-bar.dev/69/?scale=97&title=Solved&width=500&suffix=) * [2.1](https://sarabander.github.io/sicp/html/Chapter-2.xhtml#Chapter-2) Introduction to Data Abstraction - [Code in book](src/sicp/chapter_2/part_1/book_2_1.clj) * [2.1.1](https://sarabander.github.io/sicp/html/2_002e1.xhtml#g_t2_002e1_002e1) Example: Arithmetic Operations for Rational Numbers - [2.1](src/sicp/chapter_2/part_1/ex_2_01.clj) @@ -58,7 +58,7 @@ Jay Sussman on basics of computer science and software engineering. * [2.3.1](https://sarabander.github.io/sicp/html/2_002e3.xhtml#g_t2_002e3_002e1) Quotation - [2.53](src/sicp/chapter_2/part_3/ex_2_53.clj), [2.54](src/sicp/chapter_2/part_3/ex_2_54.clj), [2.55](src/sicp/chapter_2/part_3/ex_2_55.clj) * [2.3.2](https://sarabander.github.io/sicp/html/2_002e3.xhtml#g_t2_002e3_002e2) Example: Symbolic Differentiation - [2.56](src/sicp/chapter_2/part_3/ex_2_56.clj), [2.57](src/sicp/chapter_2/part_3/ex_2_57.clj), [2.58](src/sicp/chapter_2/part_3/ex_2_58.clj) * [2.3.3](https://sarabander.github.io/sicp/html/2_002e3.xhtml#g_t2_002e3_002e3) Example: Representing Sets - [2.59](src/sicp/chapter_2/part_3/ex_2_59.clj), [2.60](src/sicp/chapter_2/part_3/ex_2_60.clj), [2.61](src/sicp/chapter_2/part_3/ex_2_61.clj), [2.62](src/sicp/chapter_2/part_3/ex_2_62.clj), [2.63](src/sicp/chapter_2/part_3/ex_2_63.clj), [2.64](src/sicp/chapter_2/part_3/ex_2_64.clj), [2.65](src/sicp/chapter_2/part_3/ex_2_65.clj), [2.66](src/sicp/chapter_2/part_3/ex_2_66.clj) - * [2.3.4](https://sarabander.github.io/sicp/html/2_002e3.xhtml#g_t2_002e3_002e4) Example: Huffman Encoding Trees - [2.67](src/sicp/chapter_2/part_3/ex_2_67.clj), [2.68](src/sicp/chapter_2/part_3/ex_2_68.clj) + * [2.3.4](https://sarabander.github.io/sicp/html/2_002e3.xhtml#g_t2_002e3_002e4) Example: Huffman Encoding Trees - [2.67](src/sicp/chapter_2/part_3/ex_2_67.clj), [2.68](src/sicp/chapter_2/part_3/ex_2_68.clj), [2.69](src/sicp/chapter_2/part_3/ex_2_69.clj) * 2.4 Multiple Representations for Abstract Data * 2.4.1 Representations for Complex Numbers * 2.4.2 Tagged data diff --git a/src/sicp/chapter_2/part_3/book_2_3.clj b/src/sicp/chapter_2/part_3/book_2_3.clj index 9e3b270..91d6bc1 100644 --- a/src/sicp/chapter_2/part_3/book_2_3.clj +++ b/src/sicp/chapter_2/part_3/book_2_3.clj @@ -198,13 +198,48 @@ (empty? set) (list x) (< (weight x) (weight (first set))) (cons x set) :else (cons (first set) - (adjoin-set x (rest set))))) + (adjoin-set-h x (rest set))))) (defn make-leaf-set [pairs] (if (empty? pairs) '() (let [pair (first pairs)] - (adjoin-set + (adjoin-set-h (make-leaf (first pair) ; symbol (second pair)) ; frequency (make-leaf-set (rest pairs)))))) + +; Samples +(def huffman-pairs '((:A 4) (:B 2) (:C 1) (:D 1))) + +(def huffman-tree + (make-code-tree + (make-leaf :A 4) + (make-code-tree + (make-leaf :B 2) + (make-code-tree + (make-leaf :D 1) + (make-leaf :C 1))))) + +(def huffman-tree-as-list + '[[:leaf :A 4] + [[:leaf :B 2] + [[:leaf :D 1] + [:leaf :C 1] + (:D :C) 2] + (:B :D :C) 4] + (:A :B :D :C) 8]) + +(def huffman-message-decoded '(0 ; A + 1 1 0 ; D + 0 ; A + 1 0 ; B + 1 0 ; B + 1 1 1 ; C + 0)) ; A +(def huffman-message-encoded '(:A :D :A :B :B :C :A)) + +(def huffman-A '(0)) +(def huffman-B '(1 0)) +(def huffman-C '(1 1 1)) +(def huffman-D '(1 1 0)) diff --git a/src/sicp/chapter_2/part_3/ex_2_69.clj b/src/sicp/chapter_2/part_3/ex_2_69.clj new file mode 100644 index 0000000..21e905f --- /dev/null +++ b/src/sicp/chapter_2/part_3/ex_2_69.clj @@ -0,0 +1,35 @@ +(ns sicp.chapter-2.part-3.ex-2-69 + (:require [sicp.chapter-2.part-3.book-2-3 :as b23])) + +; Exercise 2.69 +; +; The following procedure takes as its argument a list of symbol-frequency pairs +; (where no symbol appears in more than one pair) and generates a Huffman encoding tree according +; to the Huffman algorithm. +; +; (define (generate-huffman-tree pairs) +; (successive-merge +; (make-leaf-set pairs))) +; +; Make-leaf-set is the procedure given above that transforms the list of pairs into an ordered set +; of leaves. Successive-merge is the procedure you must write, using make-code-tree to successively +; merge the smallest-weight elements of the set until there is only one element left, which is the +; desired Huffman tree. +; +; (This procedure is slightly tricky, but not really complicated. If you find yourself designing +; a complex procedure, then you are almost certainly doing something wrong.) +; +; You can take significant advantage of the fact that we are using an ordered set representation.) + +(defn successive-merge [leaf-set] + (if (= (count leaf-set) 1) + (first leaf-set) + (let [first (first leaf-set) + second (second leaf-set) + rest (drop 2 leaf-set)] + (successive-merge (b23/adjoin-set-h + (b23/make-code-tree first second) + rest))))) + +(defn generate-huffman-tree [pairs] + (successive-merge (b23/make-leaf-set pairs))) diff --git a/test/sicp/chapter_2/part_3/book_2_3_test.clj b/test/sicp/chapter_2/part_3/book_2_3_test.clj index 37a5242..00d218e 100644 --- a/test/sicp/chapter_2/part_3/book_2_3_test.clj +++ b/test/sicp/chapter_2/part_3/book_2_3_test.clj @@ -7,6 +7,8 @@ element-of-set-tree? element-of-set? entry + huffman-tree + huffman-tree-as-list intersection-set intersection-set-sorted leaf? @@ -163,8 +165,11 @@ (adjoin-set-h (make-leaf :b 4) (list (make-leaf :b 4) (make-leaf :a 8)))))) (deftest make-leaf-set-test - (is (= '([:leaf :A 4] - [:leaf :B 2] + (is (= '([:leaf :D 1] [:leaf :C 1] - [:leaf :D 1]) + [:leaf :B 2] + [:leaf :A 4]) (make-leaf-set '((:A 4) (:B 2) (:C 1) (:D 1)))))) + +(deftest huffman-examples-tests + (is (= huffman-tree-as-list huffman-tree))) diff --git a/test/sicp/chapter_2/part_3/ex_2_67_test.clj b/test/sicp/chapter_2/part_3/ex_2_67_test.clj index 373483a..c492979 100644 --- a/test/sicp/chapter_2/part_3/ex_2_67_test.clj +++ b/test/sicp/chapter_2/part_3/ex_2_67_test.clj @@ -3,18 +3,6 @@ [sicp.chapter-2.part-3.book-2-3 :as b23] [sicp.chapter-2.part-3.ex-2-67 :refer [decode-message]])) -; [[:leaf :A 4] [[:leaf :B 2] [[:leaf :D 1] [:leaf :C 1] (:D :C) 2] (:B :D :C) 4] (:A :B :D :C) 8] -(def sample-tree - (b23/make-code-tree - (b23/make-leaf :A 4) - (b23/make-code-tree - (b23/make-leaf :B 2) - (b23/make-code-tree - (b23/make-leaf :D 1) - (b23/make-leaf :C 1))))) - -(def sample-message '(0 1 1 0 0 1 0 1 0 1 1 1 0)) - (deftest decode-message-test - (is (= '(:A :D :A :B :B :C :A) - (decode-message sample-message sample-tree)))) + (is (= b23/huffman-message-encoded + (decode-message b23/huffman-message-decoded b23/huffman-tree)))) diff --git a/test/sicp/chapter_2/part_3/ex_2_68_test.clj b/test/sicp/chapter_2/part_3/ex_2_68_test.clj index 2dc21ce..5b5dc38 100644 --- a/test/sicp/chapter_2/part_3/ex_2_68_test.clj +++ b/test/sicp/chapter_2/part_3/ex_2_68_test.clj @@ -3,30 +3,21 @@ [sicp.chapter-2.part-3.book-2-3 :as b23] [sicp.chapter-2.part-3.ex-2-68 :refer [encode encode-symbol]])) -(def sample-tree - (b23/make-code-tree - (b23/make-leaf :A 4) - (b23/make-code-tree - (b23/make-leaf :B 2) - (b23/make-code-tree - (b23/make-leaf :D 1) - (b23/make-leaf :C 1))))) - (deftest encode-symbol-test - (is (= '(0) (encode-symbol :A sample-tree))) - (is (= '(1 0) (encode-symbol :B sample-tree))) - (is (= '(1 1 1) (encode-symbol :C sample-tree))) - (is (= '(1 1 0) (encode-symbol :D sample-tree)))) + (is (= b23/huffman-A (encode-symbol :A b23/huffman-tree))) + (is (= b23/huffman-B (encode-symbol :B b23/huffman-tree))) + (is (= b23/huffman-C (encode-symbol :C b23/huffman-tree))) + (is (= b23/huffman-D (encode-symbol :D b23/huffman-tree)))) (deftest encode-symbol-exception-test (is (thrown-with-msg? Exception #"Symbol not found in tree :Z" - (encode-symbol :Z sample-tree)))) + (encode-symbol :Z b23/huffman-tree)))) (deftest encode-exception-test (try - (encode-symbol :Z sample-tree) + (encode-symbol :Z b23/huffman-tree) (is false "Exception not thrown") (catch Exception e (is (= (.getMessage e) "Symbol not found in tree :Z"))))) @@ -39,4 +30,4 @@ 1 0 ; B 1 1 1 ; C 0) ; A - (encode '(:A :D :A :B :B :C :A) sample-tree)))) + (encode '(:A :D :A :B :B :C :A) b23/huffman-tree)))) diff --git a/test/sicp/chapter_2/part_3/ex_2_69_test.clj b/test/sicp/chapter_2/part_3/ex_2_69_test.clj new file mode 100644 index 0000000..84a1c94 --- /dev/null +++ b/test/sicp/chapter_2/part_3/ex_2_69_test.clj @@ -0,0 +1,14 @@ +(ns sicp.chapter-2.part-3.ex-2-69-test + (:require [clojure.test :refer [deftest is]] + [sicp.chapter-2.part-3.book-2-3 :as b23] + [sicp.chapter-2.part-3.ex-2-68 :refer [encode-symbol]] + [sicp.chapter-2.part-3.ex-2-69 :refer [generate-huffman-tree]])) + +(deftest generate-huffman-tree-test + (is (= b23/huffman-tree (generate-huffman-tree b23/huffman-pairs)))) + +(deftest encode-symbol-test + (is (= b23/huffman-A (encode-symbol :A (generate-huffman-tree b23/huffman-pairs)))) + (is (= b23/huffman-B (encode-symbol :B (generate-huffman-tree b23/huffman-pairs)))) + (is (= b23/huffman-C (encode-symbol :C (generate-huffman-tree b23/huffman-pairs)))) + (is (= b23/huffman-D (encode-symbol :D (generate-huffman-tree b23/huffman-pairs)))))