Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 14 additions & 14 deletions exercises/practice/dnd-character/test_dnd_character.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ const testing = std.testing;
const dnd_character = @import("dnd_character.zig");
const Character = dnd_character.Character;

fn isValidAbilityScore(n: isize) bool {
return n >= 3 and n <= 18;
}

fn isValid(c: Character) bool {
return isValidAbilityScore(c.strength) and
isValidAbilityScore(c.dexterity) and
isValidAbilityScore(c.constitution) and
isValidAbilityScore(c.intelligence) and
isValidAbilityScore(c.wisdom) and
isValidAbilityScore(c.charisma) and
(c.hitpoints == 10 + dnd_character.modifier(c.constitution));
}

test "ability modifier for score 3 is -4" {
const expected: i8 = -4;
const actual = dnd_character.modifier(3);
Expand Down Expand Up @@ -100,27 +114,13 @@ test "ability modifier for score 18 is +4" {
try testing.expectEqual(expected, actual);
}

fn isValidAbilityScore(n: isize) bool {
return n >= 3 and n <= 18;
}

test "random ability is within range" {
for (0..20) |_| {
const actual = dnd_character.ability();
try testing.expect(isValidAbilityScore(actual));
}
}

fn isValid(c: Character) bool {
return isValidAbilityScore(c.strength) and
isValidAbilityScore(c.dexterity) and
isValidAbilityScore(c.constitution) and
isValidAbilityScore(c.intelligence) and
isValidAbilityScore(c.wisdom) and
isValidAbilityScore(c.charisma) and
(c.hitpoints == 10 + dnd_character.modifier(c.constitution));
}

test "random character is valid" {
for (0..20) |_| {
const character = Character.init();
Expand Down
12 changes: 12 additions & 0 deletions exercises/practice/matching-brackets/.meta/supplements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"cases": [
{
"description": "maximum required level of nesting",
"property": "isPaired",
"input": {
"value": "(((_[[[_{{{_()_}}}_]]]_)))"
},
"expected": true
}
]
}
12 changes: 12 additions & 0 deletions exercises/practice/pangram/.meta/supplements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"cases": [
{
"description": "non-alphanumeric printable ASCII",
"property": "isPangram",
"input": {
"sentence": " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
},
"expected": false
}
]
}
10 changes: 10 additions & 0 deletions exercises/practice/queen-attack/.meta/supplements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"cases": [
{
"description": "queen has exactly two fields",
"property": "fields",
"input": {},
"expected": 2
}
]
}
2 changes: 2 additions & 0 deletions exercises/practice/space-age/test_space_age.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const std = @import("std");
const testing = std.testing;

const expectApproxEqAbs = std.testing.expectApproxEqAbs;

const space_age = @import("space_age.zig");
Expand Down
14 changes: 14 additions & 0 deletions exercises/practice/sum-of-multiples/.meta/supplements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"cases": [
{
"description": "sum is greater than maximum value of u32",
"property": "sum",
"comment": "Note that for a u32 `limit`, the maximum sum of multiples fits in a u64.",
"input": {
"factors": [100000000],
"limit": 1000000000
},
"expected": 4500000000
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ test "much larger factors" {
}

test "all numbers are multiples of 1" {
const expected: u64 = 4_950;
const expected: u64 = 4950;
const factors = [_]u32{1};
const limit = 100;
const actual = try sum(testing.allocator, &factors, limit);
Expand Down
16 changes: 16 additions & 0 deletions generators/exercises/acronym.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from lib import zstr

IMPORT_SELF = False

HEADER = 'const abbreviate = @import("acronym.zig").abbreviate;'


def gen_case(case):
phrase = case["input"]["phrase"]
expected = case["expected"]
return (
f" const expected = {zstr(expected)};\n"
f" const actual = try abbreviate(testing.allocator, {zstr(phrase)});\n"
f" defer testing.allocator.free(actual);\n"
f" try testing.expectEqualStrings(expected, actual);\n"
)
40 changes: 40 additions & 0 deletions generators/exercises/allergies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from lib import zint

HEADER = (
"const isAllergicTo = allergies.isAllergicTo;\n"
"const initAllergenSet = allergies.initAllergenSet;\n"
)


def describe(case, parent):
# Leaf cases carry their own description; group nodes pass it down via `parent`.
if "cases" in case:
return case["description"]
prop = case.get("property")
desc = case["description"]
if prop == "allergicTo":
return f"{case['input']['item']}: {desc}"
if prop == "list":
return f"initAllergenSet: {desc}"
return desc


def gen_case(case):
prop = case["property"]
if prop == "allergicTo":
item = case["input"]["item"]
score = case["input"]["score"]
expected = case["expected"]
bang = "" if expected else "!"
return f" try testing.expect({bang}isAllergicTo({zint(score)}, .{item}));\n"
# property == "list": exercise the EnumSet returned by initAllergenSet.
score = case["input"]["score"]
allergens = case["expected"]
s = (
f" const expected_count: usize = {len(allergens)};\n"
f" const actual = initAllergenSet({zint(score)});\n"
f" try testing.expectEqual(expected_count, actual.count());\n"
)
for a in allergens:
s += f" try testing.expect(actual.contains(.{a}));\n"
return s
43 changes: 43 additions & 0 deletions generators/exercises/anagram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from lib import zstr

IMPORT_SELF = False

HEADER = """const detectAnagrams = @import("anagram.zig").detectAnagrams;

fn testAnagrams(
allocator: std.mem.Allocator,
expected: []const []const u8,
word: []const u8,
candidates: []const []const u8,
) !void {
var actual = try detectAnagrams(allocator, word, candidates);
defer actual.deinit();
try testing.expectEqual(expected.len, actual.count());
for (expected) |e| {
try testing.expect(actual.contains(e));
}
}
"""


def _strlist(values):
if not values:
return "[_][]const u8{}"
inner = ", ".join(zstr(v) for v in values)
return f"[_][]const u8{{ {inner} }}"


def gen_case(case):
word = case["input"]["subject"]
candidates = case["input"]["candidates"]
expected = case["expected"]
return (
f" const expected = {_strlist(expected)};\n"
f" const word = {zstr(word)};\n"
f" const candidates = {_strlist(candidates)};\n"
f" try std.testing.checkAllAllocationFailures(\n"
f" std.testing.allocator,\n"
f" testAnagrams,\n"
f" .{{ &expected, word, &candidates }},\n"
f" );\n"
)
25 changes: 25 additions & 0 deletions generators/exercises/atbash_cipher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from lib import zstr

HEADER = """const encode = atbash_cipher.encode;
const decode = atbash_cipher.decode;"""


def describe(case, parent):
# The target uses only the leaf description (no parent group prefix) and
# lowercases it, so "encode OMG" becomes "encode omg".
if "cases" in case:
return None
return case["description"].lower()


def gen_case(case):
fn = case["property"] # "encode" or "decode"
phrase = case["input"]["phrase"]
expected = case["expected"]
return (
f" const expected = {zstr(expected)};\n"
f" const s = {zstr(phrase)};\n"
f" const actual = try {fn}(testing.allocator, s);\n"
f" defer testing.allocator.free(actual);\n"
f" try testing.expectEqualStrings(expected, actual);\n"
)
15 changes: 15 additions & 0 deletions generators/exercises/bob.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from lib import zstr

IMPORT_SELF = False

HEADER = 'const response = @import("bob.zig").response;'


def gen_case(case):
s = case["input"]["heyBob"]
expected = case["expected"]
return (
f" const expected = {zstr(expected)};\n"
f" const actual = response({zstr(s)});\n"
f" try testing.expectEqualStrings(expected, actual);\n"
)
20 changes: 20 additions & 0 deletions generators/exercises/collatz_conjecture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from lib import zint, is_error

IMPORT_SELF = True
HEADER = "const ComputationError = collatz_conjecture.ComputationError;"


def gen_case(case):
number = zint(case["input"]["number"])
expected = case["expected"]
if is_error(expected):
return (
" const expected = ComputationError.IllegalArgument;\n"
f" const actual = collatz_conjecture.steps({number});\n"
" try testing.expectError(expected, actual);\n"
)
return (
f" const expected: usize = {zint(expected)};\n"
f" const actual = try collatz_conjecture.steps({number});\n"
" try testing.expectEqual(expected, actual);\n"
)
27 changes: 27 additions & 0 deletions generators/exercises/darts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from lib import zfloat

IMPORT_SELF = True


def describe(case, parent):
return case["description"][0].lower() + case["description"][1:]


# The committed target flips the signs on this symmetric case (score depends only
# on x^2 + y^2, so it is equivalent to the canonical x=-3.5, y=3.5).
_INPUT_OVERRIDE = {
"just within the middle circle": {"x": 3.5, "y": -3.5},
}


def gen_case(case):
inp = _INPUT_OVERRIDE.get(case["description"], case["input"])
x = zfloat(inp["x"])
y = zfloat(inp["y"])
e = case["expected"]
return (
f" const expected: usize = {e};\n"
f" const coordinate = darts.Coordinate.init({x}, {y});\n"
f" const actual = coordinate.score();\n"
f" try testing.expectEqual(expected, actual);\n"
)
29 changes: 29 additions & 0 deletions generators/exercises/difference_of_squares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from lib import zint

IMPORT_SELF = True

_LABEL = {
"squareOfSum": "square of sum up to",
"sumOfSquares": "sum of squares up to",
"differenceOfSquares": "difference of squares up to",
}


def describe(case, parent):
# Leaf cases carry a property; group nodes do not. Group descriptions are
# dropped so leaves get a clean "<label> up to <n>" name.
if "cases" in case:
return None
number = case["input"]["number"]
return f"{_LABEL[case['property']]} {number}"


def gen_case(case):
number = zint(case["input"]["number"])
expected = zint(case["expected"])
prop = case["property"]
return (
f" const expected: usize = {expected};\n"
f" const actual = difference_of_squares.{prop}({number});\n"
" try testing.expectEqual(expected, actual);\n"
)
55 changes: 55 additions & 0 deletions generators/exercises/dnd_character.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
HEADER = """const Character = dnd_character.Character;

fn isValidAbilityScore(n: isize) bool {
return n >= 3 and n <= 18;
}

fn isValid(c: Character) bool {
return isValidAbilityScore(c.strength) and
isValidAbilityScore(c.dexterity) and
isValidAbilityScore(c.constitution) and
isValidAbilityScore(c.intelligence) and
isValidAbilityScore(c.wisdom) and
isValidAbilityScore(c.charisma) and
(c.hitpoints == 10 + dnd_character.modifier(c.constitution));
}"""


def describe(case, parent):
# The "ability modifier" group is flattened away; use just the leaf description.
return case["description"]


def order_key(case):
# Property tests for the random generators come after all modifier cases.
if case["property"] == "ability":
return 1
if case["property"] == "character":
return 2
return 0


def gen_case(case):
prop = case["property"]
if prop == "modifier":
score = case["input"]["score"]
expected = case["expected"]
return (
f" const expected: i8 = {expected};\n"
f" const actual = dnd_character.modifier({score});\n"
f" try testing.expectEqual(expected, actual);\n"
)
if prop == "ability":
return (
" for (0..20) |_| {\n"
" const actual = dnd_character.ability();\n"
" try testing.expect(isValidAbilityScore(actual));\n"
" }\n"
)
# character
return (
" for (0..20) |_| {\n"
" const character = Character.init();\n"
" try testing.expect(isValid(character));\n"
" }\n"
)
Loading
Loading