From e2ec24027ca957a73315bcfa65db133e2c9fde62 Mon Sep 17 00:00:00 2001 From: hucancode Date: Wed, 29 Jan 2025 17:40:34 +0900 Subject: [PATCH 1/4] add robot name exercise (test runner still have memory leaks) --- exercises/practice/robot-name/robot_name.odin | 70 +++++++++++ .../practice/robot-name/robot_name_test.odin | 118 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 exercises/practice/robot-name/robot_name.odin create mode 100644 exercises/practice/robot-name/robot_name_test.odin diff --git a/exercises/practice/robot-name/robot_name.odin b/exercises/practice/robot-name/robot_name.odin new file mode 100644 index 0000000..cd466c8 --- /dev/null +++ b/exercises/practice/robot-name/robot_name.odin @@ -0,0 +1,70 @@ +package robotname + +import "core:fmt" +import "core:math/rand" + +RobotStorage :: struct { + names: map[string]bool, +} + +Robot :: struct { + name: string, +} + +Error :: enum { + CouldNotCreateName, +} + +make_storage :: proc() -> RobotStorage { + return RobotStorage{make(map[string]bool)} +} +delete_storage :: proc(storage: ^RobotStorage) { + for k in storage.names { + delete(k) + } + delete(storage.names) +} + +new_robot :: proc(storage: ^RobotStorage) -> (Robot, Error) { + name, e := create_name(storage) + return Robot{name}, e +} + +reset :: proc(storage: ^RobotStorage, r: ^Robot) { + delete_key(&storage.names, r.name) + name, err := create_name(storage) + if err != nil { + return + } + delete_string(r.name) + r.name = name +} + +letters := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +numbers := "0123456789" +random_name :: proc() -> string { + ret := make([]u8, 5) + for i in 0 ..< 2 { + k := rand.int_max(len(letters)) + ret[i] = letters[k] + } + for i in 2 ..< 5 { + k := rand.int_max(len(numbers)) + ret[i] = numbers[k] + } + return string(ret) +} + +create_name :: proc(storage: ^RobotStorage) -> (string, Error) { + max_tries := 100 + for i in 0 ..< max_tries { + key := random_name() + if key in storage.names { + delete(key) + continue + } + storage.names[key] = true + return key, nil + } + return "", Error.CouldNotCreateName +} diff --git a/exercises/practice/robot-name/robot_name_test.odin b/exercises/practice/robot-name/robot_name_test.odin new file mode 100644 index 0000000..6b9cb0f --- /dev/null +++ b/exercises/practice/robot-name/robot_name_test.odin @@ -0,0 +1,118 @@ +package robotname + +import "core:fmt" +import "core:strings" +import "core:testing" +import "core:text/regex" + +name_valid :: proc(name: string) -> bool { + pat, err := regex.create(`^[A-Z]{2}\d{3}$`) + defer regex.destroy(pat) + if err != regex.Creation_Error.None { + return false + } + captures, matched := regex.match(pat, name) + defer regex.destroy(captures) + return matched +} + +@(test) +test_name_valid :: proc(t: ^testing.T) { + storage := make_storage() + defer delete_storage(&storage) + r, e := new_robot(&storage) + testing.expect(t, e == nil) + testing.expect(t, name_valid(r.name)) +} + +@(test) +test_successive_robots_have_different_names :: proc(t: ^testing.T) { + storage := make_storage() + defer delete_storage(&storage) + n1, e1 := new_robot(&storage) + n2, e2 := new_robot(&storage) + testing.expect(t, e1 == nil) + testing.expect(t, e2 == nil) + testing.expect(t, n1 != n2) +} + +@(test) +test_reset_name :: proc(t: ^testing.T) { + storage := make_storage() + defer delete_storage(&storage) + r, e := new_robot(&storage) + n1 := r.name + reset(&storage, &r) + n2 := r.name + testing.expect(t, e == nil) + testing.expect(t, n1 != n2) +} + +@(test) +test_multiple_names :: proc(t: ^testing.T) { + n := 100 + storage := make_storage() + defer delete_storage(&storage) + seen := make(map[string]bool) + defer delete(seen) + for i := 0; i <= n; i += 1 { + r, e := new_robot(&storage) + testing.expect(t, e == nil) + testing.expect(t, !seen[r.name]) + seen[r.name] = true + } +} + +dfs_fill_names :: proc(storage: ^RobotStorage) { + GO_BACK_SENTINEL := u8('-') + NAME_LENGTH := 5 + LETTERS := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + NUMBERS := "0123456789" + stack := make([dynamic]u8) + defer delete(stack) + current := make([]u8, NAME_LENGTH) + defer delete(current) + for i in 0 ..< len(LETTERS) { + append(&stack, LETTERS[i]) + } + depth := 0 + for len(stack) > 0 { + ch := pop(&stack) + go_back := ch == GO_BACK_SENTINEL + if go_back { + depth -= 1 + continue + } + current[depth] = ch + depth += 1 + if depth == NAME_LENGTH { + key := string(current) + storage.names[key] = true + n := len(storage.names) + if n % 8673 == 0 { + fmt.printfln("[%d] '%s',", n, key) + } + depth -= 1 + continue + } + append(&stack, GO_BACK_SENTINEL) + if depth < 2 { + for i in 0 ..< len(LETTERS) { + append(&stack, LETTERS[i]) + } + } else { + for i in 0 ..< len(NUMBERS) { + append(&stack, NUMBERS[i]) + } + } + } +} + +@(test) +test_collisions :: proc(t: ^testing.T) { + storage := make_storage() + defer delete_storage(&storage) + dfs_fill_names(&storage) + r, e := new_robot(&storage) + testing.expect_value(t, e, Error.CouldNotCreateName) +} From 8dcb5e3994e7fb24888dde3c42dd0c4f5b7bf695 Mon Sep 17 00:00:00 2001 From: hucancode Date: Wed, 29 Jan 2025 18:41:26 +0900 Subject: [PATCH 2/4] fix test speed --- exercises/practice/robot-name/robot_name_test.odin | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/exercises/practice/robot-name/robot_name_test.odin b/exercises/practice/robot-name/robot_name_test.odin index 6b9cb0f..c77cf24 100644 --- a/exercises/practice/robot-name/robot_name_test.odin +++ b/exercises/practice/robot-name/robot_name_test.odin @@ -87,11 +87,7 @@ dfs_fill_names :: proc(storage: ^RobotStorage) { depth += 1 if depth == NAME_LENGTH { key := string(current) - storage.names[key] = true - n := len(storage.names) - if n % 8673 == 0 { - fmt.printfln("[%d] '%s',", n, key) - } + storage.names[strings.clone(key)] = true depth -= 1 continue } @@ -116,3 +112,4 @@ test_collisions :: proc(t: ^testing.T) { r, e := new_robot(&storage) testing.expect_value(t, e, Error.CouldNotCreateName) } + From 27734227e6df8c4b3c899a13d556cb53768e2e7b Mon Sep 17 00:00:00 2001 From: hucancode Date: Thu, 30 Jan 2025 10:21:50 +0900 Subject: [PATCH 3/4] add armstrong numbers & pangram --- .../armstrong-numbers/armstrong_numbers.odin | 45 +++++++++ .../armstrong_numbers_test.odin | 99 +++++++++++++++++++ exercises/practice/pangram/pangram.odin | 17 ++++ exercises/practice/pangram/pangram_test.odin | 52 ++++++++++ 4 files changed, 213 insertions(+) create mode 100644 exercises/practice/armstrong-numbers/armstrong_numbers.odin create mode 100644 exercises/practice/armstrong-numbers/armstrong_numbers_test.odin create mode 100644 exercises/practice/pangram/pangram.odin create mode 100644 exercises/practice/pangram/pangram_test.odin diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers.odin b/exercises/practice/armstrong-numbers/armstrong_numbers.odin new file mode 100644 index 0000000..08828ec --- /dev/null +++ b/exercises/practice/armstrong-numbers/armstrong_numbers.odin @@ -0,0 +1,45 @@ +package armstrong_numbers + +to_digits :: proc(n: u128) -> [dynamic]u8 { + ret := make([dynamic]u8) + k := n + for k > 0 { + append(&ret, u8(k % 10)) + k /= 10 + } + return ret +} + +fast_pow :: proc(base: u8, exp: int) -> u128 { + if exp == 0 { + return 1 + } + if base < 2 { + return u128(base) + } + ret := u128(1) + a := u128(base) + n := exp + for { + if n % 2 == 1 { + ret *= a + } + n >>= 1 + if n == 0 { + break + } + a *= a + } + return ret +} + +is_armstrong_number :: proc(n: u128) -> bool { + digits := to_digits(n) + defer delete(digits) + power := len(digits) + sum: u128 = 0 + for d in digits { + sum += fast_pow(d, power) + } + return sum == n +} diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.odin b/exercises/practice/armstrong-numbers/armstrong_numbers_test.odin new file mode 100644 index 0000000..30632ef --- /dev/null +++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.odin @@ -0,0 +1,99 @@ +package armstrong_numbers +import "core:testing" + +@(test) +test_zero :: proc(t: ^testing.T) { + testing.expect(t, is_armstrong_number(0), "zero is an armstrong number") +} + +@(test) +test_single_digit :: proc(t: ^testing.T) { + testing.expect(t, is_armstrong_number(5), "single-digit numbers are armstrong numbers") +} + +@(test) +test_two_digit :: proc(t: ^testing.T) { + testing.expect(t, !is_armstrong_number(10), "there are no two-digit armstrong numbers") +} + +@(test) +test_three_digit_armstrong :: proc(t: ^testing.T) { + testing.expect(t, is_armstrong_number(153), "three-digit number that is an armstrong number") +} + +@(test) +test_three_digit_non_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + !is_armstrong_number(100), + "three-digit number that is not an armstrong number", + ) +} + +@(test) +test_four_digit_armstrong :: proc(t: ^testing.T) { + testing.expect(t, is_armstrong_number(9_474), "four-digit number that is an armstrong number") +} + +@(test) +test_four_digit_non_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + !is_armstrong_number(9_475), + "four-digit number that is not an armstrong number", + ) +} + +@(test) +test_seven_digit_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + is_armstrong_number(9_926_315), + "seven-digit number that is an armstrong number", + ) +} + +@(test) +test_seven_digit_non_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + !is_armstrong_number(9_926_314), + "seven-digit number that is not an armstrong number", + ) +} + +@(test) +test_33_digit_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + is_armstrong_number(186_709_961_001_538_790_100_634_132_976_990), + "33-digit number that is an armstrong number", + ) +} + +@(test) +test_38_digit_non_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + !is_armstrong_number(99_999_999_999_999_999_999_999_999_999_999_999_999), + "38-digit number that is not an armstrong number", + ) +} + +@(test) +test_largest_armstrong :: proc(t: ^testing.T) { + testing.expect( + t, + is_armstrong_number(115_132_219_018_763_992_565_095_597_973_971_522_401), + "the largest and last armstrong number", + ) +} + +@(test) +test_largest_u128 :: proc(t: ^testing.T) { + testing.expect( + t, + !is_armstrong_number(340_282_366_920_938_463_463_374_607_431_768_211_455), + "the largest 128-bit unsigned integer value is not an armstrong number", + ) +} diff --git a/exercises/practice/pangram/pangram.odin b/exercises/practice/pangram/pangram.odin new file mode 100644 index 0000000..65a8708 --- /dev/null +++ b/exercises/practice/pangram/pangram.odin @@ -0,0 +1,17 @@ +package pangram +is_pangram :: proc(str: string) -> bool { + vis := 0 + expected := (1 << 26) - 1 + for i in 0 ..< len(str) { + c := str[i] + if c >= 'a' && c <= 'z' { + c -= 'a' + } else if c >= 'A' && c <= 'Z' { + c -= 'A' + } else { + continue + } + vis |= 1 << c + } + return vis == expected +} diff --git a/exercises/practice/pangram/pangram_test.odin b/exercises/practice/pangram/pangram_test.odin new file mode 100644 index 0000000..db0c14a --- /dev/null +++ b/exercises/practice/pangram/pangram_test.odin @@ -0,0 +1,52 @@ +package pangram +import "core:testing" + +@(test) +test_sentence_empty :: proc(t: ^testing.T) { + testing.expect(t, !is_pangram("")) +} + +@(test) +test_pangram_with_only_lower_case :: proc(t: ^testing.T) { + testing.expect(t, is_pangram("the quick brown fox jumps over the lazy dog")) +} + +@(test) +test_missing_character_x :: proc(t: ^testing.T) { + testing.expect(t, !is_pangram("a quick movement of the enemy will jeopardize five gunboats")) +} + +@(test) +test_another_missing_character_x :: proc(t: ^testing.T) { + testing.expect(t, !is_pangram("the quick brown fish jumps over the lazy dog")) +} + +@(test) +test_pangram_with_underscores :: proc(t: ^testing.T) { + testing.expect(t, is_pangram("the_quick_brown_fox_jumps_over_the_lazy_dog")) +} + +@(test) +test_pangram_with_numbers :: proc(t: ^testing.T) { + testing.expect(t, is_pangram("the 1 quick brown fox jumps over the 2 lazy dogs")) +} + +@(test) +test_missing_letters_replaced_by_numbers :: proc(t: ^testing.T) { + testing.expect(t, !is_pangram("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog")) +} + +@(test) +test_pangram_with_mixed_case_and_punctuation :: proc(t: ^testing.T) { + testing.expect(t, is_pangram("\"Five quacking Zephyrs jolt my wax bed.\"")) +} + +@(test) +unique_26_characters_but_not_pangram :: proc(t: ^testing.T) { + testing.expect(t, !is_pangram("abcdefghijklm ABCDEFGHIJKLM")) +} + +@(test) +non_alphanumeric_printable :: proc(t: ^testing.T) { + testing.expect(t, !is_pangram(" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")) +} From c8d9c90133605a514d633db91503e0e731b95d77 Mon Sep 17 00:00:00 2001 From: hucancode Date: Fri, 31 Jan 2025 16:16:26 +0900 Subject: [PATCH 4/4] add food chain exercise --- exercises/practice/food-chain/food_chain.odin | 93 +++++++++ .../practice/food-chain/food_chain_test.odin | 179 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 exercises/practice/food-chain/food_chain.odin create mode 100644 exercises/practice/food-chain/food_chain_test.odin diff --git a/exercises/practice/food-chain/food_chain.odin b/exercises/practice/food-chain/food_chain.odin new file mode 100644 index 0000000..e0092d8 --- /dev/null +++ b/exercises/practice/food-chain/food_chain.odin @@ -0,0 +1,93 @@ +package food_chain + +import "core:strings" + +Animal :: struct { + name: string, + name_long: Maybe(string), + phrase: string, + next: Maybe(^Animal), +} + +fly := Animal { + name = "fly", + phrase = "I don't know why she swallowed the fly. Perhaps she'll die.", +} +spider := Animal { + name = "spider", + name_long = "spider that wriggled and jiggled and tickled inside her", + phrase = "It wriggled and jiggled and tickled inside her.", + next = &fly, +} +bird := Animal { + name = "bird", + phrase = "How absurd to swallow a bird!", + next = &spider, +} +cat := Animal { + name = "cat", + phrase = "Imagine that, to swallow a cat!", + next = &bird, +} +dog := Animal { + name = "dog", + phrase = "What a hog, to swallow a dog!", + next = &cat, +} +goat := Animal { + name = "goat", + phrase = "Just opened her throat and swallowed a goat!", + next = &dog, +} +cow := Animal { + name = "cow", + phrase = "I don't know how she swallowed a cow!", + next = &goat, +} +horse := Animal { + name = "horse", + phrase = "She's dead, of course!", +} + +verses := [8]Animal{fly, spider, bird, cat, dog, goat, cow, horse} + +generate_verse :: proc(builder: ^strings.Builder, i: int) { + animal: Maybe(^Animal) = &verses[i] + node, ok := animal.? + if !ok { + return + } + strings.write_string(builder, "I know an old lady who swallowed a ") + strings.write_string(builder, node.name) + strings.write_string(builder, ".\n") + if node.next != nil { + strings.write_string(builder, node.phrase) + strings.write_rune(builder, '\n') + } + for { + node := animal.? or_break + if node.next == nil { + strings.write_string(builder, node.phrase) + strings.write_rune(builder, '\n') + } else { + next := node.next.? + strings.write_string(builder, "She swallowed the ") + strings.write_string(builder, node.name) + strings.write_string(builder, " to catch the ") + name := next.name_long.? or_else next.name + strings.write_string(builder, name) + strings.write_string(builder, ".\n") + } + animal = node.next + } +} + +recite :: proc(start, end: int) -> string { + builder := strings.builder_make() + defer strings.builder_destroy(&builder) + for i in start - 1 ..< end { + generate_verse(&builder, i) + strings.write_rune(&builder, '\n') + } + return strings.trim_right_space(strings.to_string(builder)) +} diff --git a/exercises/practice/food-chain/food_chain_test.odin b/exercises/practice/food-chain/food_chain_test.odin new file mode 100644 index 0000000..abf07ed --- /dev/null +++ b/exercises/practice/food-chain/food_chain_test.odin @@ -0,0 +1,179 @@ +package food_chain +import "core:testing" + +@(test) +test_fly :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(1, 1) + testing.expect_value(t, actual, expected) +} +@(test) +test_spider :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a spider. +It wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(2, 2) + testing.expect_value(t, actual, expected) +} +@(test) +test_bird :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a bird. +How absurd to swallow a bird! +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(3, 3) + testing.expect_value(t, actual, expected) +} +@(test) +test_cat :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a cat. +Imagine that, to swallow a cat! +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(4, 4) + testing.expect_value(t, actual, expected) +} +@(test) +test_dog :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a dog. +What a hog, to swallow a dog! +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(5, 5) + testing.expect_value(t, actual, expected) +} +@(test) +test_goat :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a goat. +Just opened her throat and swallowed a goat! +She swallowed the goat to catch the dog. +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(6, 6) + testing.expect_value(t, actual, expected) +} +@(test) +test_cow :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a cow. +I don't know how she swallowed a cow! +She swallowed the cow to catch the goat. +She swallowed the goat to catch the dog. +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(7, 7) + testing.expect_value(t, actual, expected) +} +@(test) +test_horse :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a horse. +She's dead, of course!` + + + actual := recite(8, 8) + testing.expect_value(t, actual, expected) +} +@(test) +test_multiple_verse :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a spider. +It wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a bird. +How absurd to swallow a bird! +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die.` + + + actual := recite(1, 3) + testing.expect_value(t, actual, expected) +} +@(test) +test_full_song :: proc(t: ^testing.T) { + expected := `I know an old lady who swallowed a fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a spider. +It wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a bird. +How absurd to swallow a bird! +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a cat. +Imagine that, to swallow a cat! +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a dog. +What a hog, to swallow a dog! +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a goat. +Just opened her throat and swallowed a goat! +She swallowed the goat to catch the dog. +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a cow. +I don't know how she swallowed a cow! +She swallowed the cow to catch the goat. +She swallowed the goat to catch the dog. +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a horse. +She's dead, of course!` + + + actual := recite(1, 8) + testing.expect_value(t, actual, expected) +}