Create the Sieve of Eratosthenes exercise. (#101)
Also, a script to automate adding new exercises.
This commit is contained in:
103
bin/add-exercise
Executable file
103
bin/add-exercise
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if (( $# != 1 )); then
|
||||
echo >&2 "Populates a new directory for a practice exercise."
|
||||
echo >&2 "Usage: $0 <exercise-slug>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
die() { echo >&2 "$*"; exit 1; }
|
||||
|
||||
required_tool() {
|
||||
command -v "$1" >/dev/null 2>&1 ||
|
||||
die "$1 is required but not installed. Please install it and make sure it's in your PATH."
|
||||
}
|
||||
required_tool jq
|
||||
required_tool curl
|
||||
|
||||
[[ -f ./bin/fetch-configlet ]] || die "run this script from the repo's root directory."
|
||||
|
||||
|
||||
slug="${1}"
|
||||
name=$(echo "${slug}" | sed 's/\b\w/\u&/g; s/-/ /g') # assumes GNU sed
|
||||
|
||||
it_exists=$(
|
||||
jq --arg slug "${slug}" '
|
||||
.exercises.practice
|
||||
| map(select(.slug == $slug))
|
||||
| length > 0
|
||||
' config.json
|
||||
)
|
||||
[[ ${it_exists} == false ]] || die "${slug} already exists in config.json"
|
||||
|
||||
# Add entry for exercise in config.json
|
||||
./bin/fetch-configlet
|
||||
jq --arg slug "${slug}" \
|
||||
--arg uuid "$(./bin/configlet uuid)" \
|
||||
--arg name "${name}" \
|
||||
'
|
||||
.exercises.practice += [
|
||||
{
|
||||
slug: $slug,
|
||||
name: $name,
|
||||
uuid: $uuid,
|
||||
practices: [],
|
||||
prerequisites: [],
|
||||
difficulty: 1
|
||||
}
|
||||
]
|
||||
' config.json > config.json.tmp \
|
||||
&& mv config.json.tmp config.json
|
||||
|
||||
# Sync the exercise
|
||||
./bin/configlet sync --update --yes \
|
||||
--tests include \
|
||||
--metadata \
|
||||
--docs \
|
||||
--exercise "${slug}"
|
||||
|
||||
cp lib/test-words.8th "exercises/practice/${slug}"
|
||||
|
||||
cat << END_TEST > "exercises/practice/${slug}/test.8th"
|
||||
"test-words.8th" dup . cr f:include
|
||||
"${slug}.8th" dup . cr f:include
|
||||
"${slug}-tests.8th" dup . cr f:include
|
||||
bye
|
||||
END_TEST
|
||||
|
||||
touch "exercises/practice/${slug}/${slug}.8th"
|
||||
touch "exercises/practice/${slug}/.meta/example.8th"
|
||||
curl --silent "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/${slug}/canonical-data.json" > "exercises/practice/${slug}/${slug}-tests.8th"
|
||||
|
||||
echo
|
||||
read -rp 'Your github username: ' author
|
||||
conf="exercises/practice/${slug}/.meta/config.json"
|
||||
jq --arg slug "${slug}" \
|
||||
--arg author "${author}" \
|
||||
'
|
||||
.authors = [$author] |
|
||||
.files = {
|
||||
solution: [$slug + ".8th"],
|
||||
test: [$slug + "-tests.8th"],
|
||||
example: [".meta/example.8th"]
|
||||
}
|
||||
' "${conf}" > "${conf}.tmp" \
|
||||
&& mv "${conf}.tmp" "${conf}"
|
||||
|
||||
echo
|
||||
ls -laR "exercises/practice/${slug}"
|
||||
|
||||
cat << NEXT_STEPS
|
||||
|
||||
Your next steps are:
|
||||
- Create the test suite in 'exercises/practice/${slug}/${slug}-tests.8th'
|
||||
based on the canonical data at 'https://github.com/exercism/problem-specifications/blob/main/exercises/${slug}/canonical-data.json'
|
||||
- Any test cases you don't implement, mark them in 'exercises/practice/${slug}/.meta/tests.toml' with "include = false"
|
||||
- Create the example solution in 'exercises/practice/${slug}/.meta/example.8th'
|
||||
- Verify the example solution by running 'bin/test-no-docker ${slug}'
|
||||
- Create the stub solution in 'exercises/practice/${slug}/${slug}.8th'
|
||||
- Update the 'difficulty' value for the exercise's entry in the 'config.json' file
|
||||
- Validate CI using 'bin/configlet lint'
|
||||
NEXT_STEPS
|
||||
@@ -234,6 +234,14 @@
|
||||
"prerequisites": [],
|
||||
"difficulty": 9,
|
||||
"practices": []
|
||||
},
|
||||
{
|
||||
"slug": "sieve",
|
||||
"name": "Sieve",
|
||||
"uuid": "e31eac0d-cece-440e-a2be-1431e36284bf",
|
||||
"practices": [],
|
||||
"prerequisites": [],
|
||||
"difficulty": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
28
exercises/practice/sieve/.docs/instructions.md
Normal file
28
exercises/practice/sieve/.docs/instructions.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Instructions
|
||||
|
||||
Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers.
|
||||
|
||||
A prime number is a number that is only divisible by 1 and itself.
|
||||
For example, 2, 3, 5, 7, 11, and 13 are prime numbers.
|
||||
|
||||
The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime.
|
||||
|
||||
A number that is **not** prime is called a "composite number".
|
||||
|
||||
To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number.
|
||||
Then you repeat the following steps:
|
||||
|
||||
1. Find the next unmarked number in your list. This is a prime number.
|
||||
2. Mark all the multiples of that prime number as composite (not prime).
|
||||
|
||||
You keep repeating these steps until you've gone through every number in your list.
|
||||
At the end, all the unmarked numbers are prime.
|
||||
|
||||
~~~~exercism/note
|
||||
[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm.
|
||||
|
||||
The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes.
|
||||
A good first test is to check that you do not use division or remainder operations.
|
||||
|
||||
[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
|
||||
~~~~
|
||||
7
exercises/practice/sieve/.docs/introduction.md
Normal file
7
exercises/practice/sieve/.docs/introduction.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Introduction
|
||||
|
||||
You bought a big box of random computer parts at a garage sale.
|
||||
You've started putting the parts together to build custom computers.
|
||||
|
||||
You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare.
|
||||
You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits.
|
||||
19
exercises/practice/sieve/.meta/config.json
Normal file
19
exercises/practice/sieve/.meta/config.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"authors": [
|
||||
"glennj"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"sieve.8th"
|
||||
],
|
||||
"test": [
|
||||
"sieve-tests.8th"
|
||||
],
|
||||
"example": [
|
||||
".meta/example.8th"
|
||||
]
|
||||
},
|
||||
"blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.",
|
||||
"source": "Sieve of Eratosthenes at Wikipedia",
|
||||
"source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes"
|
||||
}
|
||||
53
exercises/practice/sieve/.meta/example.8th
Normal file
53
exercises/practice/sieve/.meta/example.8th
Normal file
@@ -0,0 +1,53 @@
|
||||
(* from https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Algorithm_and_variants
|
||||
*
|
||||
* algorithm Sieve of Eratosthenes is
|
||||
* input: an integer n > 1.
|
||||
* output: all prime numbers from 2 through n.
|
||||
*
|
||||
* let A be an array of Boolean values, indexed by integers 2 to n,
|
||||
* initially all set to true.
|
||||
*
|
||||
* for i = 2, 3, 4, ..., not exceeding √n do
|
||||
* if A[i] is true
|
||||
* for j = i2, i2+i, i2+2i, i2+3i, ..., not exceeding n do
|
||||
* set A[j] := false
|
||||
*
|
||||
* return all i such that A[i] is true.
|
||||
*)
|
||||
|
||||
: make-flag-array \ -- a
|
||||
( drop true ) 0 r@ a:generate
|
||||
0 false a:!
|
||||
1 false a:!
|
||||
;
|
||||
|
||||
: mark-multiples-of \ a i -- a
|
||||
2dup a:@ !if 2drop ;; then
|
||||
drop
|
||||
dup dup n:* \ a i idx (initial idx is i^2)
|
||||
repeat
|
||||
third over \ a i idx a idx
|
||||
false a:! \ a i idx a
|
||||
drop over n:+
|
||||
dup r@ n:>
|
||||
until!
|
||||
2drop
|
||||
;
|
||||
|
||||
: mark-multiples \ a -- a
|
||||
' mark-multiples-of 2 r@ n:sqrt loop
|
||||
;
|
||||
|
||||
: extract-primes \ a1 -- a2
|
||||
a:new >r
|
||||
( if r> a:push >r else drop then ) a:each
|
||||
drop r>
|
||||
;
|
||||
|
||||
: primes \ n -- a
|
||||
>r
|
||||
make-flag-array
|
||||
mark-multiples
|
||||
extract-primes
|
||||
rdrop
|
||||
;
|
||||
25
exercises/practice/sieve/.meta/tests.toml
Normal file
25
exercises/practice/sieve/.meta/tests.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
|
||||
[88529125-c4ce-43cc-bb36-1eb4ddd7b44f]
|
||||
description = "no primes under two"
|
||||
|
||||
[4afe9474-c705-4477-9923-840e1024cc2b]
|
||||
description = "find first prime"
|
||||
|
||||
[974945d8-8cd9-4f00-9463-7d813c7f17b7]
|
||||
description = "find primes up to 10"
|
||||
|
||||
[2e2417b7-3f3a-452a-8594-b9af08af6d82]
|
||||
description = "limit is prime"
|
||||
|
||||
[92102a05-4c7c-47de-9ed0-b7d5fcd00f21]
|
||||
description = "find primes up to 1000"
|
||||
40
exercises/practice/sieve/sieve-tests.8th
Normal file
40
exercises/practice/sieve/sieve-tests.8th
Normal file
@@ -0,0 +1,40 @@
|
||||
5 tests
|
||||
|
||||
"no primes under two"
|
||||
[]
|
||||
( 1 primes )
|
||||
test_array_eq_num
|
||||
|
||||
SKIP-REST-OF-TESTS
|
||||
|
||||
"find first prime"
|
||||
[2]
|
||||
( 2 primes )
|
||||
test_array_eq_num
|
||||
|
||||
"find primes up to 10"
|
||||
[2, 3, 5, 7]
|
||||
( 10 primes )
|
||||
test_array_eq_num
|
||||
|
||||
"limit is prime"
|
||||
[2, 3, 5, 7, 11, 13]
|
||||
( 13 primes )
|
||||
test_array_eq_num
|
||||
|
||||
"find primes up to 1000"
|
||||
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
|
||||
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131,
|
||||
137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223,
|
||||
227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311,
|
||||
313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
|
||||
419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503,
|
||||
509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613,
|
||||
617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719,
|
||||
727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827,
|
||||
829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
|
||||
947, 953, 967, 971, 977, 983, 991, 997]
|
||||
( 1000 primes )
|
||||
test_array_eq_num
|
||||
|
||||
end-of-tests
|
||||
2
exercises/practice/sieve/sieve.8th
Normal file
2
exercises/practice/sieve/sieve.8th
Normal file
@@ -0,0 +1,2 @@
|
||||
: primes \ n -- a
|
||||
;
|
||||
168
exercises/practice/sieve/test-words.8th
Normal file
168
exercises/practice/sieve/test-words.8th
Normal file
@@ -0,0 +1,168 @@
|
||||
needs console/loaded
|
||||
|
||||
-1 var, test-count
|
||||
var tests-passed
|
||||
var tests-failed
|
||||
var tests-skipped
|
||||
|
||||
true var, run-test
|
||||
: SKIP-REST-OF-TESTS false run-test ! ;
|
||||
|
||||
: tests \ n --
|
||||
test-count !
|
||||
;
|
||||
|
||||
: test-passed \ s --
|
||||
1 tests-passed n:+!
|
||||
con:green con:onBlack . space " ... OK" . con:white con:onBlack cr
|
||||
;
|
||||
|
||||
: test-skipped \ s --
|
||||
1 tests-skipped n:+!
|
||||
con:cyan con:onBlack . space " ... SKIPPED" . con:white con:onBlack cr
|
||||
;
|
||||
|
||||
: test-failed \ s --
|
||||
1 tests-failed n:+!
|
||||
con:red con:onBlack . space " ... FAIL" . con:white con:onBlack cr
|
||||
;
|
||||
|
||||
: isword? \ x -- x f
|
||||
dup >kind ns:w n:=
|
||||
;
|
||||
|
||||
: run-test? \ -- T
|
||||
run-test @ if true else "RUN_ALL_TESTS" getenv n:>bool then
|
||||
;
|
||||
|
||||
: test_eq \ s x w -- | s w x --
|
||||
run-test? !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
n:=
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
: test_eqs \ s x w -- | s w x --
|
||||
run-test? !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
s:=
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
: test_true \ s w --
|
||||
run-test? !if drop test-skipped ;; then
|
||||
w:exec
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
: test_false \ s w --
|
||||
run-test? !if drop test-skipped ;; then
|
||||
w:exec
|
||||
if test-failed else test-passed then
|
||||
;
|
||||
|
||||
: test_null \ s w --
|
||||
run-test? !if drop test-skipped ;; then
|
||||
w:exec
|
||||
null? nip
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
\ compare arrays by testing elements with string equality
|
||||
: test_eqa \ s x w -- | s w x --
|
||||
run-test? !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
' s:= a:= 2nip
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
\ Compare a1 to a2. Individual elements are compared with w (e.g., n:cmp).
|
||||
\ The result n is:
|
||||
\ - The first non-zero result of ( a1[i] a2[i] w ), or
|
||||
\ - n:cmp of the lengths of a1 and a2
|
||||
\ Note: This may be used in some test files. For example, to compare results
|
||||
\ that are not required to be in a certain order.
|
||||
: a:cmp SED: a1 a2 w -- n
|
||||
>r
|
||||
over a:len nip over a:len nip n:cmp
|
||||
true mark -rot \ Stack: length-cmp a1 a2
|
||||
( r@ w:exec nip dup if break else drop then ) a:2each
|
||||
rdrop \ Done with comparison word
|
||||
2drop \ Done with a1 and a2
|
||||
mark?
|
||||
!if \ Got a non-zero result from a compare
|
||||
nip
|
||||
then
|
||||
;
|
||||
|
||||
\ compare arrays by testing elements with numeric equality
|
||||
: test_array_eq_num \ s x w -- | s w x --
|
||||
run-test? !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
' n:cmp a:cmp 0 n:=
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
\ Test that array a is equal to the result of word w. Compare arrays by
|
||||
\ testing elements with array equality. The SED of w is -- a1, where a1
|
||||
\ is an array of arrays. The elements of each sub-array must be numbers.
|
||||
: test_eqa2 SED: s a w --
|
||||
run-test? dup . cr !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
.s
|
||||
( ' n:= a:= nip nip ) a:= nip nip
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
: test_map_eq \ s m w -- | s w m --
|
||||
run-test? !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
' n:= m:= 2nip
|
||||
if test-passed else test-failed then
|
||||
;
|
||||
|
||||
: test_map_neq \ s m w -- | s w m --
|
||||
run-test? !if 2drop test-skipped ;; then
|
||||
isword? !if swap then
|
||||
w:exec
|
||||
' n:= m:= 2nip
|
||||
if test-failed else test-passed then
|
||||
;
|
||||
|
||||
\ Num passed + num skipped + num failed should == num tests
|
||||
: all-tests-run? \ -- T
|
||||
tests-passed @ tests-skipped @ tests-failed @ n:+ n:+
|
||||
test-count @ n:=
|
||||
;
|
||||
|
||||
( all-tests-run?
|
||||
!if con:red con:onBlack "... FAIL - not all tests completed" . con:white con:onBlack cr then
|
||||
) onexit
|
||||
|
||||
\ Print a summary of the tests run
|
||||
( con:white con:onBlack
|
||||
test-count @ . space "tests planned - " .
|
||||
tests-passed @ . space "passed - " .
|
||||
tests-skipped @ . space "skipped - " .
|
||||
tests-failed @ . space "failed" . cr
|
||||
) onexit
|
||||
|
||||
\ Set the exit status:
|
||||
\ 0 = all OK
|
||||
\ 1 = not all tests were run (some error occurred)
|
||||
\ 2 = some tests failed
|
||||
: end-of-tests \ --
|
||||
all-tests-run?
|
||||
if
|
||||
tests-failed @ 0 n:= if 0 else 2 then
|
||||
else
|
||||
1
|
||||
then
|
||||
die
|
||||
;
|
||||
4
exercises/practice/sieve/test.8th
Normal file
4
exercises/practice/sieve/test.8th
Normal file
@@ -0,0 +1,4 @@
|
||||
"test-words.8th" dup . cr f:include
|
||||
"sieve.8th" dup . cr f:include
|
||||
"sieve-tests.8th" dup . cr f:include
|
||||
bye
|
||||
Reference in New Issue
Block a user