Skip to content

Commit 6080ffa

Browse files
Add scripts to verify exercises
1 parent 8142d38 commit 6080ffa

File tree

96 files changed

+1939
-621
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+1939
-621
lines changed

bin/verify-exercises

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
3+
# Synopsis:
4+
# Verify that each exercise's example/exemplar solution passes the tests.
5+
# You can either verify all exercises or a single exercise.
6+
7+
# Example: verify all exercises
8+
# bin/verify-exercises
9+
10+
# Example: verify single exercise
11+
# bin/verify-exercises two-fer
12+
13+
set -eo pipefail
14+
15+
die() { echo "$*" >&2; exit 1; }
16+
17+
required_tool() {
18+
command -v "${1}" >/dev/null 2>&1 ||
19+
die "${1} is required but not installed. Please install it and make sure it's in your PATH."
20+
}
21+
22+
required_tool bb
23+
24+
bb test.clj "$@"

bin/verify-exercises-in-docker

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env bash
2+
3+
# Synopsis:
4+
# Verify that each exercise's example/exemplar solution passes the tests
5+
# using the track's test runner Docker image.
6+
# You can either verify all exercises or a single exercise.
7+
8+
# Example: verify all exercises in Docker
9+
# bin/verify-exercises-in-docker
10+
11+
# Example: verify single exercise in Docker
12+
# bin/verify-exercises-in-docker two-fer
13+
14+
set -eo pipefail
15+
16+
die() { echo "$*" >&2; exit 1; }
17+
18+
required_tool() {
19+
command -v "${1}" >/dev/null 2>&1 ||
20+
die "${1} is required but not installed. Please install it and make sure it's in your PATH."
21+
}
22+
23+
required_tool docker
24+
25+
copy_example_or_examplar_to_solution() {
26+
jq -c '[.files.solution, .files.exemplar // .files.example] | transpose | map({src: .[1], dst: .[0]}) | .[]' .meta/config.json \
27+
| while read -r src_and_dst; do
28+
cp "$(jq -r '.src' <<< "${src_and_dst}")" "$(jq -r '.dst' <<< "${src_and_dst}")"
29+
done
30+
}
31+
32+
pull_docker_image() {
33+
# shellcheck disable=SC1083
34+
docker pull exercism/clojure-test-runner ||
35+
die $'Could not find the `exercism/clojure-test-runner` Docker image.\nCheck the test runner docs at https://exercism.org/docs/building/tooling/test-runners for more information.'
36+
}
37+
38+
run_tests() {
39+
local slug
40+
slug="${1}"
41+
42+
# shellcheck disable=SC1083
43+
docker run \
44+
--rm \
45+
--network none \
46+
--read-only \
47+
--mount type=bind,src="${PWD}",dst=/solution \
48+
--mount type=bind,src="${PWD}",dst=/output \
49+
--mount type=tmpfs,dst=/tmp \
50+
exercism/clojure-test-runner "${slug}" /solution /output
51+
jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
52+
}
53+
54+
verify_exercise() {
55+
local dir
56+
local slug
57+
local tmp_dir
58+
dir=$(realpath "${1}")
59+
slug=$(basename "${dir}")
60+
tmp_dir=$(mktemp -d -t "exercism-verify-${slug}-XXXXX")
61+
62+
echo "Verifying ${slug} exercise..."
63+
64+
(
65+
trap 'rm -rf "$tmp_dir"' EXIT # remove tempdir when subshell ends
66+
cp -r "${dir}/." "${tmp_dir}"
67+
cd "${tmp_dir}"
68+
69+
copy_example_or_examplar_to_solution
70+
run_tests "${slug}"
71+
)
72+
}
73+
74+
verify_exercises() {
75+
local exercise_slug
76+
exercise_slug="${1}"
77+
78+
shopt -s nullglob
79+
count=0
80+
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
81+
if [[ -d "${exercise_dir}" ]]; then
82+
verify_exercise "${exercise_dir}"
83+
((++count))
84+
fi
85+
done
86+
((count > 0)) || die 'no matching exercises found!'
87+
}
88+
89+
pull_docker_image
90+
91+
exercise_slug="${1:-*}"
92+
verify_exercises "${exercise_slug}"

exercises/concept/annalyns-infiltration/src/annalyns_infiltration.clj

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
(defn can-fast-attack?
44
"Returns true if a fast-attack can be made, false otherwise."
55
[knight-awake?]
6-
)
6+
(not knight-awake?))
77

88
(defn can-spy?
99
"Returns true if the kidnappers can be spied upon, false otherwise."
1010
[knight-awake? archer-awake? prisoner-awake?]
11-
)
11+
(or knight-awake? archer-awake? prisoner-awake?))
1212

1313
(defn can-signal-prisoner?
1414
"Returns true if the prisoner can be signalled, false otherwise."
1515
[archer-awake? prisoner-awake?]
16-
)
16+
(and (not archer-awake?)
17+
prisoner-awake?))
1718

1819
(defn can-free-prisoner?
1920
"Returns true if prisoner can be freed, false otherwise."
2021
[knight-awake? archer-awake? prisoner-awake? dog-present?]
21-
)
22+
(or (and (not knight-awake?)
23+
(not archer-awake?)
24+
prisoner-awake?)
25+
(and (not archer-awake?)
26+
dog-present?)))
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
(ns bird-watcher)
22

3-
(def last-week
4-
)
3+
(def last-week [0 2 5 3 7 8 4])
54

65
(defn today [birds]
7-
)
6+
(peek birds))
87

98
(defn inc-bird [birds]
10-
)
9+
(update birds (dec (count birds)) inc))
1110

1211
(defn day-without-birds? [birds]
13-
)
12+
(not (every? pos? birds)))
1413

1514
(defn n-days-count [birds n]
16-
)
15+
(reduce + (take n birds)))
1716

1817
(defn busy-days [birds]
19-
)
18+
(count (filter #(>= % 5) birds)))
2019

2120
(defn odd-week? [birds]
22-
)
21+
(= birds [1 0 1 0 1 0 1]))

exercises/concept/card-games/src/card_games.clj

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,47 @@
44
"Takes the current round number and returns
55
a `list` with that round and the _next two_."
66
[n]
7-
)
7+
(list n (inc n) (+ n 2)))
88

9-
(defn concat-rounds
9+
(defn concat-rounds
1010
"Takes two lists and returns a single `list`
1111
consisting of all the rounds in the first `list`,
1212
followed by all the rounds in the second `list`"
1313
[l1 l2]
14-
)
14+
(concat l1 l2))
1515

16-
(defn contains-round?
16+
(defn contains-round?
1717
"Takes a list of rounds played and a round number.
1818
Returns `true` if the round is in the list, `false` if not."
1919
[l n]
20-
)
20+
(boolean (some #{n} l)))
2121

2222
(defn card-average
2323
"Returns the average value of a hand"
2424
[hand]
25-
)
25+
(double (/ (apply + hand) (count hand))))
2626

2727
(defn approx-average?
2828
"Returns `true` if average is equal to either one of:
2929
- Take the average of the _first_ and _last_ number in the hand.
3030
- Using the median (middle card) of the hand."
3131
[hand]
32-
)
32+
(or (= (card-average hand) (double (/ (+ (first hand) (last hand)) 2)))
33+
(= (card-average hand) (double (nth hand (int (/ (count hand) 2.0)))))))
3334

3435
(defn average-even-odd?
3536
"Returns true if the average of the cards at even indexes
3637
is the same as the average of the cards at odd indexes."
3738
[hand]
38-
)
39+
(let [evens (map #(nth hand %) (range 1 (count hand) 2))
40+
odds (map #(nth hand %) (range 0 (count hand) 2))]
41+
(= (double (/ (apply + evens) (count evens)))
42+
(double (/ (apply + odds) (count odds))))))
3943

4044
(defn maybe-double-last
4145
"If the last card is a Jack (11), doubles its value
4246
before returning the hand."
4347
[hand]
44-
)
48+
(if (= 11 (last hand))
49+
(concat (butlast hand) '(22))
50+
hand))
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
(ns cars-assemble)
22

3+
(def default-rate 221)
4+
5+
(defn success-rate [speed]
6+
(cond (= speed 10) 0.77
7+
(= speed 9) 0.8
8+
(>= speed 5) 0.9
9+
:else 1.0))
10+
311
(defn production-rate
412
"Returns the assembly line's production rate per hour,
513
taking into account its success rate"
614
[speed]
7-
)
15+
(* (* default-rate speed)
16+
(success-rate speed)))
817

918
(defn working-items
1019
"Calculates how many working cars are produced per minute"
1120
[speed]
12-
)
21+
(int (quot (production-rate speed) 60)))
Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,41 @@
11
(ns coordinate-transformation)
22

3-
(defn translate2d
3+
(defn translate2d
44
"Returns a function making use of a closure to
55
perform a repeatable 2d translation of a coordinate pair."
66
[dx dy]
7-
)
7+
(fn [x y] [(+ dx x) (+ dy y)]))
88

9-
(defn scale2d
9+
(defn scale2d
1010
"Returns a function making use of a closure to
1111
perform a repeatable 2d scale of a coordinate pair."
1212
[sx sy]
13-
)
13+
(fn [x y] [(* sx x) (* sy y)]))
1414

1515
(defn compose-transform
1616
"Create a composition function that returns a function that
1717
combines two functions to perform a repeatable transformation."
1818
[f g]
19-
)
19+
(fn [x y]
20+
(let [f-result (f x y)]
21+
(g (first f-result) (last f-result)))))
2022

2123
(defn memoize-transform
2224
"Returns a function that memoizes the last result.
2325
If the arguments are the same as the last call,
2426
the memoized result is returned."
2527
[f]
26-
)
28+
(let [mem (atom {:last-x nil
29+
:last-y nil
30+
:last-result nil})]
31+
(fn [& args]
32+
(if-let [e (and
33+
(= (first args) (:last-x @mem))
34+
(= (last args) (:last-y @mem))
35+
(:last-result @mem))]
36+
e
37+
(let [ret (apply f args)]
38+
(swap! mem assoc :last-x (first args))
39+
(swap! mem assoc :last-y (last args))
40+
(swap! mem assoc :last-result ret)
41+
ret)))))
Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,58 @@
11
(ns date-parser)
22

3-
(def day)
4-
(def month)
5-
(def year)
3+
(def day "\\d{1,2}")
4+
(def month "\\d{1,2}")
5+
(def year "\\d{4}")
66

7-
(def days)
7+
(def days "Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday")
88

99
(defn day-names [s]
10-
)
10+
(re-matches (re-pattern days) s))
1111

12-
(def months)
12+
(def months "January|February|March|April|May|June|July|August|September|October|November|December")
1313

1414
(defn month-names [s]
15-
)
15+
(re-matches (re-pattern months) s))
1616

1717
(defn capture-month [s]
18-
)
18+
(let [matcher (re-matcher (re-pattern (str "(?<month>" month ")")) s)]
19+
(when (.matches matcher)
20+
{:month (.group matcher "month")})))
1921

2022
(defn capture-day [s]
21-
)
23+
(let [matcher (re-matcher (re-pattern (str "(?<day>" day ")")) s)]
24+
(when (.matches matcher)
25+
{:day (.group matcher "day")})))
2226

2327
(defn capture-year [s]
24-
)
28+
(let [matcher (re-matcher (re-pattern (str "(?<year>" year ")")) s)]
29+
(when (.matches matcher)
30+
{:year (.group matcher "year")})))
2531

2632
(defn capture-month-name [s]
27-
)
33+
{:month-name (first (re-find (re-pattern (str "(?<month>" months ")")) s))})
2834

2935
(defn capture-day-name [s]
30-
)
36+
{:day-name (first (re-find (re-pattern (str "(?<day>" days ")")) s))})
3137

3238
(defn capture-numeric-date [s]
33-
)
39+
(let [matcher (re-matcher (re-pattern (str "(?<day>" day ")/(?<month>" month ")/(?<year>" year ")")) s)]
40+
(when (.matches matcher)
41+
{:day (.group matcher "day")
42+
:month (.group matcher "month")
43+
:year (.group matcher "year")})))
3444

3545
(defn capture-month-name-date [s]
36-
)
46+
(let [matcher (re-matcher (re-pattern (str "(?<month>" months ") (?<day>" day "), (?<year>" year ")")) s)]
47+
(when (.matches matcher)
48+
{:month-name (.group matcher "month")
49+
:day (.group matcher "day")
50+
:year (.group matcher "year")})))
3751

3852
(defn capture-day-month-name-date [s]
39-
)
53+
(let [matcher (re-matcher (re-pattern (str "(?<dayname>" days "), (?<month>" months ") (?<day>" day "), (?<year>" year ")")) s)]
54+
(when (.matches matcher)
55+
{:day-name (.group matcher "dayname")
56+
:month-name (.group matcher "month")
57+
:day (.group matcher "day")
58+
:year (.group matcher "year")})))

0 commit comments

Comments
 (0)