Skip to content

Commit

Permalink
change: Sync tests (#788)
Browse files Browse the repository at this point in the history
* sync tests

* add generators and regenerate tests

* update starter file

* update contributors

* update example solution

* update prerequisites

* mark update-test-case as non private function

* regenerate tests

[no important files changed]
  • Loading branch information
tasxatzial authored Jan 19, 2025
1 parent ac40704 commit 733c763
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 65 deletions.
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -818,8 +818,9 @@
"uuid": "7945eb6a-20eb-489d-90bd-020df3d9e2cb",
"practices": [],
"prerequisites": [
"conditionals",
"numbers",
"vectors"
"lists"
],
"difficulty": 3,
"topics": [
Expand Down
3 changes: 2 additions & 1 deletion exercises/practice/change/.meta/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"AndreaCrotti",
"haus",
"sjwarner-bp",
"yurrriq"
"yurrriq",
"tasxatzial"
],
"files": {
"solution": [
Expand Down
64 changes: 43 additions & 21 deletions exercises/practice/change/.meta/example.clj
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
;; Taken from https://exercism.org/tracks/clojure/exercises/change/solutions/Testare
;; and adjusted to pass the new tests

(ns change)

(def algo
(memoize (fn [amount coins]
(let [smaller (filter #(<= % amount) coins)]
(if (empty? smaller) [amount]
(apply min-key
count
; check if valid solution, i.e. the final amount was zero
(filter (comp zero? first)
(map
(fn [coin]
(concat
(algo (rem amount coin) smaller)
(repeat (quot amount coin) coin)))
smaller))))))))
(defn- find-decrements [coins]
"Returns an iterable function that takes a vector pair of [edges graph] and returns
a pair of [new-edges new-graph], where the new edges are obtained by subtracting all
coins from all edges, removing any that are already in the graph or negative values."
(fn [[edges graph]]
(if (empty? edges)
(throw (IllegalArgumentException. "can't make target with given coins"))
(let [new-graph (for [coin coins
edge edges
:when (<= coin edge)]
[(- edge coin) coin])]
[(set (remove graph (map first new-graph))) (merge (into {} new-graph) graph)]))))

(defn- trail-map [target coins]
"Returns a map of amount to the coin that leads to the next amount."
; e.g., (trail-map 27 #{1 7 20 25}) returns
; {0 20, 7 20, 20 7, 27 nil, 1 25, 13 7, 6 1, 25 1, 2 25, 19 1, 26 1}
; You can follow this map from 0 all the way to 27
; The value of 0 in the map is 20
; the value of 20 in the map is 7
; the value of 27 in the map is nil, because it is the target
; Thus you can get change for 27 using a 20 coin and a 7 coin.
(some
#(and ((first %) 0) (second %)) ; repeat until we find a map with 0 in it
(iterate (find-decrements coins) [#{target} {target nil}])))

(defn- trail [target coins]
"Returns the coins needed to get to target using denominations supplied (by using the trail-map)"
(let [tmap (trail-map target coins)]
(loop [sum 0 coins ()]
(if-let [next-coin (tmap sum)]
(recur (+ sum next-coin) (conj coins next-coin))
coins))))

(defn issue [amount coins]
(try
(let [[x & xs] (algo amount coins)]
(if (zero? x) xs
(throw (IllegalArgumentException. "cannot change"))))
; thrown by apply min-key if we don't have the right coins to issue change
(catch clojure.lang.ArityException e (throw (IllegalArgumentException. "cannot change")))))
(defn issue [target coins]
"Returns the coins needed to get to target using denominations supplied"
(cond
(zero? target) ()
(neg? target) (throw (IllegalArgumentException. "target can't be negative"))
(< target (apply min coins)) (throw (IllegalArgumentException. "can't make target with given coins"))
:else (sort (trail target coins))))
14 changes: 14 additions & 0 deletions exercises/practice/change/.meta/generator.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(ns change-generator)

(defn- update-expected [expected]
(if-let [error (:error expected)]
{:error (str "^" error "$")}
(apply list expected)))

(defn- update-input [input]
(update input :coins #(into (sorted-set) %)))

(defn update-test-case [test-case]
(-> test-case
(update :expected update-expected)
(update :input update-input)))
14 changes: 14 additions & 0 deletions exercises/practice/change/.meta/generator.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(ns change-test
(:require [clojure.test :refer [deftest testing is]]
change))
{{#test_cases.findFewestCoins}}
(deftest issue_test_{{idx}}
(testing {{description}}
{{~#if error}}
(is (thrown-with-msg? IllegalArgumentException #{{error}}
(change/issue {{input.target}} {{input.coins}})))))
{{else}}
(is (= {{expected}}
(change/issue {{input.target}} {{input.coins}})))))
{{/if~}}
{{/test_cases.findFewestCoins~}}
19 changes: 16 additions & 3 deletions exercises/practice/change/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# This is an auto-generated file. Regular comments will be removed when this
# file is regenerated. Regenerating will not touch any manually added keys,
# so comments can be added in a "comment" key.
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[d0ebd0e1-9d27-4609-a654-df5c0ba1d83a]
description = "change for 1 cent"

[36887bea-7f92-4a9c-b0cc-c0e886b3ecc8]
description = "single coin change"
Expand All @@ -23,6 +33,9 @@ description = "possible change without unit coins available"
[9a166411-d35d-4f7f-a007-6724ac266178]
description = "another possible change without unit coins available"

[ce0f80d5-51c3-469d-818c-3e69dbd25f75]
description = "a greedy approach is not optimal"

[bbbcc154-e9e9-4209-a4db-dd6d81ec26bb]
description = "no coins make 0 change"

Expand Down
9 changes: 6 additions & 3 deletions exercises/practice/change/src/change.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
(ns change)

(defn issue [] ;; <- arglist goes here
;; your code goes here
)
(defn issue
"Given an amount to change and a set of coins, it returns the
fewest coins such that the sum of their values equals the change"
[amount coins]
;; function body
)
103 changes: 67 additions & 36 deletions exercises/practice/change/test/change_test.clj
Original file line number Diff line number Diff line change
@@ -1,38 +1,69 @@
(ns change-test
(:require [clojure.test :refer [deftest is]]
[change :refer [issue]]))
(:require [clojure.test :refer [deftest testing is]]
change))

(deftest single-coin-change
(is (= (issue 25 #{1 5 10 25 100})
'(25))))

(deftest multiple-coin-change
(is (= (issue 15 #{1 5 10 25 100})
'(5 10))))

(deftest change-with-lilliputian-coins
(is (= (issue 23 #{1 4 15 20 50})
'(4 4 15))))

(deftest change-with-elbonia-coins
(is (= (issue 63 #{1 5 10 21 25})
'(21 21 21))))

(deftest large-target-values
(is (= (issue 999 #{1 2 5 10 20 50 100})
'(2 2 5 20 20 50 100 100 100 100 100 100 100 100 100))))

(deftest no-coins-make-zero-change
(is (empty? (issue 0 #{1, 5, 10, 21, 25}))))

(deftest error-testing-for-change-smallet-than-the-smallest-coin
(is (thrown-with-msg? IllegalArgumentException #"cannot change"
(issue 3 #{5 10}))))

(deftest cannot-find-negative-change-values
(is (thrown-with-msg? IllegalArgumentException #"cannot change"
(issue -5 #{1 2 5}))))

(deftest error-testing-for-no-valid-change
(is (thrown-with-msg? IllegalArgumentException #"cannot change"
(issue 10 #{20 8 3}))))
(deftest issue_test_1
(testing "change for 1 cent"
(is (= '(1)
(change/issue 1 #{1 5 10 25})))))

(deftest issue_test_2
(testing "single coin change"
(is (= '(25)
(change/issue 25 #{1 5 10 25 100})))))

(deftest issue_test_3
(testing "multiple coin change"
(is (= '(5 10)
(change/issue 15 #{1 5 10 25 100})))))

(deftest issue_test_4
(testing "change with Lilliputian Coins"
(is (= '(4 4 15)
(change/issue 23 #{1 4 15 20 50})))))

(deftest issue_test_5
(testing "change with Lower Elbonia Coins"
(is (= '(21 21 21)
(change/issue 63 #{1 5 10 21 25})))))

(deftest issue_test_6
(testing "large target values"
(is (= '(2 2 5 20 20 50 100 100 100 100 100 100 100 100 100)
(change/issue 999 #{1 2 5 10 20 50 100})))))

(deftest issue_test_7
(testing "possible change without unit coins available"
(is (= '(2 2 2 5 10)
(change/issue 21 #{2 5 10 20 50})))))

(deftest issue_test_8
(testing "another possible change without unit coins available"
(is (= '(4 4 4 5 5 5)
(change/issue 27 #{4 5})))))

(deftest issue_test_9
(testing "a greedy approach is not optimal"
(is (= '(10 10)
(change/issue 20 #{1 10 11})))))

(deftest issue_test_10
(testing "no coins make 0 change"
(is (= '()
(change/issue 0 #{1 5 10 21 25})))))

(deftest issue_test_11
(testing "error testing for change smaller than the smallest of coins"
(is (thrown-with-msg? IllegalArgumentException #"^can't make target with given coins$"
(change/issue 3 #{5 10})))))

(deftest issue_test_12
(testing "error if no combination can add up to target"
(is (thrown-with-msg? IllegalArgumentException #"^can't make target with given coins$"
(change/issue 94 #{5 10})))))

(deftest issue_test_13
(testing "cannot find negative change values"
(is (thrown-with-msg? IllegalArgumentException #"^target can't be negative$"
(change/issue -5 #{1 2 5})))))

0 comments on commit 733c763

Please sign in to comment.