Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ bin/configlet
.psc-ide-port

.merlin
_opam
84 changes: 84 additions & 0 deletions bin/add-practice-exercise
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash

# Synopsis:
# Scaffold the files for a new practice exercise.
# After creating the exercise, follow the instructions in the output.

# Example:
# bin/add-practice-exercise two-fer

# Example with difficulty:
# bin/add-practice-exercise -d 5 two-fer

# Example with author and difficulty:
# bin/add-practice-exercise -a foo -d 3 two-fer

set -euo pipefail
scriptname=$0

help_and_exit() {
echo >&2 "Scaffold the files for a new practice exercise."
echo >&2 "Usage: ${scriptname} [-h] [-a author] [-d difficulty] <exercise-slug>"
echo >&2 "Where: author is the GitHub username of the exercise creator."
echo >&2 "Where: difficulty is between 1 (easiest) to 10 (hardest)."
exit 1
}

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."
}

require_files_template() {
jq -e --arg key "${1}" '.files[$key] | length > 0' config.json > /dev/null ||
die "The '.files.${1}' array in the 'config.json' file is empty. Please add at least one file. See https://exercism.org/docs/building/tracks/config-json#h-files for more information."
}

required_tool jq

require_files_template "solution"
require_files_template "test"
require_files_template "example"

[[ -f ./bin/fetch-configlet ]] || die "Run this script from the repo's root directory."

author=''
difficulty='1'
while getopts :ha:d: opt; do
case $opt in
h) help_and_exit ;;
a) author=$OPTARG ;;
d) difficulty=$OPTARG ;;
?) echo >&2 "Unknown option: -$OPTARG"; help_and_exit ;;
esac
done
shift "$((OPTIND - 1))"

(( $# >= 1 )) || help_and_exit

slug="${1}"

if [[ -z "${author}" ]]; then
read -rp 'Your GitHub username: ' author
fi

./bin/fetch-configlet
./bin/configlet create --practice-exercise "${slug}" --author "${author}" --difficulty "${difficulty}"

exercise_dir="exercises/practice/${slug}"
files=$(jq -r --arg dir "${exercise_dir}" '.files | to_entries | map({key: .key, value: (.value | map("'"'"'" + $dir + "/" + . + "'"'"'") | join(" and "))}) | from_entries' "${exercise_dir}/.meta/config.json")

cat << NEXT_STEPS

Your next steps are:
- Create the test suite in $(jq -r '.test' <<< "${files}")
- The tests should be 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 $(jq -r '.example' <<< "${files}")
- Verify the example solution passes the tests by running 'bin/verify-exercises ${slug}'
- Create the stub solution in $(jq -r '.solution' <<< "${files}")
- Update the 'difficulty' value for the exercise's entry in the 'config.json' file in the repo's root
- Validate CI using 'bin/configlet lint' and 'bin/configlet fmt'
NEXT_STEPS
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 3
},
{
"slug": "collatz-conjecture",
"name": "Collatz Conjecture",
"uuid": "eedc9471-326b-4498-b763-5b1c8eedca88",
"practices": [],
"prerequisites": [],
"difficulty": 3
}
]
},
Expand Down
3 changes: 3 additions & 0 deletions exercises/practice/collatz-conjecture/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Instructions

Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture.
28 changes: 28 additions & 0 deletions exercises/practice/collatz-conjecture/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Introduction

One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea.
On one page, a single question stood out: **Can every number find its way to 1?**
It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades.

The rules were deceptively simple.
Pick any positive integer.

- If it's even, divide it by 2.
- If it's odd, multiply it by 3 and add 1.

Then, repeat these steps with the result, continuing indefinitely.

Curious, you picked number 12 to test and began the journey:

12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1

Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing.
At first, the sequence seemed unpredictable — jumping up, down, and all over.
Yet, the conjecture claims that no matter the starting number, we'll always end at 1.

It was fascinating, but also puzzling.
Why does this always seem to work?
Could there be a number where the process breaks down, looping forever or escaping into infinity?
The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets.

[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/
22 changes: 22 additions & 0 deletions exercises/practice/collatz-conjecture/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"authors": [
"therealowenrees"
],
"files": {
"solution": [
"collatz_conjecture.ml"
],
"test": [
"test.ml"
],
"example": [
".meta/example.ml"
],
"editor": [
"collatz_conjecture.mli"
]
},
"blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture"
}
13 changes: 13 additions & 0 deletions exercises/practice/collatz-conjecture/.meta/example.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
let collatz_conjecture n =
let rec aux n count =
match n with
| 1 -> Ok count
| _ ->
(match n mod 2 with
| 0 -> aux (n / 2) (count + 1)
| _ -> aux (n * 3 + 1) (count + 1)
)
in
if n < 1 then Error "Only positive integers are allowed"
else aux n 0

38 changes: 38 additions & 0 deletions exercises/practice/collatz-conjecture/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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.

[540a3d51-e7a6-47a5-92a3-4ad1838f0bfd]
description = "zero steps for one"

[3d76a0a6-ea84-444a-821a-f7857c2c1859]
description = "divide if even"

[754dea81-123c-429e-b8bc-db20b05a87b9]
description = "even and odd steps"

[ecfd0210-6f85-44f6-8280-f65534892ff6]
description = "large number of even and odd steps"

[7d4750e6-def9-4b86-aec7-9f7eb44f95a3]
description = "zero is an error"
include = false

[2187673d-77d6-4543-975e-66df6c50e2da]
description = "zero is an error"
reimplements = "7d4750e6-def9-4b86-aec7-9f7eb44f95a3"

[c6c795bf-a288-45e9-86a1-841359ad426d]
description = "negative value is an error"
include = false

[ec11f479-56bc-47fd-a434-bcd7a31a7a2e]
description = "negative value is an error"
reimplements = "c6c795bf-a288-45e9-86a1-841359ad426d"
9 changes: 9 additions & 0 deletions exercises/practice/collatz-conjecture/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
default: clean test

test:
dune runtest

clean:
dune clean

.PHONY: clean
2 changes: 2 additions & 0 deletions exercises/practice/collatz-conjecture/collatz_conjecture.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let collatz_conjecture _ =
failwith "'collatz_conjecture' is missing"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val collatz_conjecture : int -> (int, string) result
20 changes: 20 additions & 0 deletions exercises/practice/collatz-conjecture/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
(executable
(name test)
(libraries base ounit2))

(alias
(name runtest)
(deps
(:x test.exe))
(action
(run %{x})))

(alias
(name buildtest)
(deps
(:x test.exe)))

(env
(dev
(flags
(:standard -warn-error -A))))
1 change: 1 addition & 0 deletions exercises/practice/collatz-conjecture/dune-project
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(lang dune 1.1)
20 changes: 20 additions & 0 deletions exercises/practice/collatz-conjecture/test.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
open Base
open OUnit2
open Collatz_conjecture

(* Assert Equals *)
(* let ae exp got _test_ctxt =
assert_equal exp got *)
let ae expected actual _ctx = assert_equal expected actual

let tests = [
"zero steps for one" >:: ae (Ok 0) (collatz_conjecture 1);
"divide if even" >:: ae (Ok 4) (collatz_conjecture 16);
"even and odd steps" >:: ae (Ok 9) (collatz_conjecture 12);
"large number of even and odd steps" >:: ae (Ok 152) (collatz_conjecture 1000000);
"zero is an error" >:: ae (Error "Only positive integers are allowed") (collatz_conjecture 0);
"negative value is an error" >:: ae (Error "Only positive integers are allowed") (collatz_conjecture (-15));
]

let () =
run_test_tt_main ("collatz_conjecture tests" >::: tests)
Loading