From 2d2b282782f8613fc44d5a68bcf66ddf55d26252 Mon Sep 17 00:00:00 2001 From: Derk-Jan Karrenbeld Date: Sat, 10 Apr 2021 05:49:48 +0200 Subject: [PATCH 1/2] Add proofs and stubs for all practice exercises --- .../proof.ci.ts} | 2 +- .../practice/accumulate/accumulate.test.ts | 2 +- exercises/practice/accumulate/accumulate.ts | 3 + exercises/practice/acronym/.meta/proof.ci.ts | 12 + exercises/practice/acronym/acronym.example.ts | 15 - exercises/practice/acronym/acronym.test.ts | 16 +- exercises/practice/acronym/acronym.ts | 6 +- .../practice/all-your-base/.meta/proof.ci.ts | 59 + .../all-your-base/all-your-base.example.ts | 61 - .../all-your-base/all-your-base.test.ts | 48 +- .../practice/all-your-base/all-your-base.ts | 7 + .../proof.ci.ts} | 4 +- .../practice/allergies/allergies.test.ts | 2 +- exercises/practice/allergies/allergies.ts | 13 + .../practice/alphametics/.meta/proof.ci.ts | 128 ++ .../alphametics/alphametics.example.ts | 138 --- .../practice/alphametics/alphametics.test.ts | 18 +- exercises/practice/alphametics/alphametics.ts | 3 + .../{anagram.example.ts => .meta/proof.ci.ts} | 4 +- exercises/practice/anagram/anagram.test.ts | 29 +- exercises/practice/anagram/anagram.ts | 9 + .../armstrong-numbers/.meta/proof.ci.ts | 7 + .../armstrong-numbers.example.ts | 11 - .../armstrong-numbers.test.ts | 18 +- .../armstrong-numbers/armstrong-numbers.ts | 3 + .../practice/atbash-cipher/.meta/proof.ci.ts | 31 + .../atbash-cipher/atbash-cipher.example.ts | 39 - .../atbash-cipher/atbash-cipher.test.ts | 34 +- .../practice/atbash-cipher/atbash-cipher.ts | 7 + .../practice/beer-song/.meta/proof.ci.ts | 35 + .../practice/beer-song/beer-song.example.ts | 39 - .../practice/beer-song/beer-song.test.ts | 14 +- exercises/practice/beer-song/beer-song.ts | 10 + .../proof.ci.ts} | 4 +- .../binary-search-tree.test.ts | 10 +- .../binary-search-tree/binary-search-tree.ts | 25 + .../practice/binary-search/.meta/proof.ci.ts | 18 + .../binary-search/binary-search.example.ts | 41 - .../binary-search/binary-search.test.ts | 60 +- .../practice/binary-search/binary-search.ts | 4 +- exercises/practice/bob/.meta/proof.ci.ts | 31 + exercises/practice/bob/bob.example.ts | 35 - exercises/practice/bob/bob.test.ts | 54 +- exercises/practice/bob/bob.ts | 8 +- exercises/practice/bowling/.meta/proof.ci.ts | 169 +++ exercises/practice/bowling/bowling.example.ts | 90 -- exercises/practice/bowling/bowling.test.ts | 318 +++-- exercises/practice/bowling/bowling.ts | 9 + .../proof.ci.ts} | 4 +- .../circular-buffer/circular-buffer.test.ts | 6 +- .../circular-buffer/circular-buffer.ts | 35 + .../{clock.example.ts => .meta/proof.ci.ts} | 0 exercises/practice/clock/clock.test.ts | 2 +- exercises/practice/clock/clock.ts | 21 + .../collatz-conjecture/.meta/proof.ci.ts | 16 + .../collatz-conjecture.example.ts | 20 - .../collatz-conjecture.test.ts | 14 +- .../collatz-conjecture/collatz-conjecture.ts | 8 +- .../proof.ci.ts} | 0 .../complex-numbers/complex-numbers.test.ts | 2 +- .../complex-numbers/complex-numbers.ts | 41 + .../{connect.example.ts => .meta/proof.ci.ts} | 2 +- exercises/practice/connect/connect.test.ts | 22 +- exercises/practice/connect/connect.ts | 9 + .../practice/crypto-square/.meta/proof.ci.ts | 62 + .../crypto-square/crypto-square.example.ts | 59 - .../crypto-square/crypto-square.test.ts | 69 +- .../practice/crypto-square/crypto-square.ts | 9 + .../proof.ci.ts} | 2 +- .../practice/custom-set/custom-set.test.ts | 2 +- exercises/practice/custom-set/custom-set.ts | 41 + exercises/practice/diamond/.meta/proof.ci.ts | 48 + exercises/practice/diamond/diamond.example.ts | 51 - exercises/practice/diamond/diamond.test.ts | 27 +- exercises/practice/diamond/diamond.ts | 3 + .../proof.ci.ts} | 2 +- .../difference-of-squares.test.ts | 2 +- .../difference-of-squares.ts | 17 + .../practice/diffie-hellman/.meta/proof.ci.ts | 113 ++ .../diffie-hellman/diffie-hellman.example.ts | 1045 ----------------- .../diffie-hellman/diffie-hellman.test.ts | 116 +- .../practice/diffie-hellman/diffie-hellman.ts | 13 + .../etl/{etl.example.ts => .meta/proof.ci.ts} | 4 +- exercises/practice/etl/etl.test.ts | 2 +- exercises/practice/etl/etl.ts | 6 +- .../practice/flatten-array/.meta/proof.ci.ts | 9 + .../flatten-array/flatten-array.example.ts | 13 - .../flatten-array/flatten-array.test.ts | 23 +- .../practice/flatten-array/flatten-array.ts | 3 + .../practice/food-chain/.meta/proof.ci.ts | 72 ++ .../practice/food-chain/food-chain.example.ts | 74 -- .../practice/food-chain/food-chain.test.ts | 22 +- exercises/practice/food-chain/food-chain.ts | 7 + .../proof.ci.ts} | 2 +- .../practice/gigasecond/gigasecond.test.ts | 2 +- exercises/practice/gigasecond/gigasecond.ts | 8 +- .../proof.ci.ts} | 0 .../grade-school/grade-school.test.ts | 130 +- .../practice/grade-school/grade-school.ts | 13 + exercises/practice/grains/.meta/proof.ci.ts | 17 + exercises/practice/grains/grains.example.ts | 21 - exercises/practice/grains/grains.test.ts | 24 +- exercises/practice/grains/grains.ts | 7 + exercises/practice/hamming/.meta/proof.ci.ts | 15 + exercises/practice/hamming/hamming.example.ts | 19 - exercises/practice/hamming/hamming.test.ts | 20 +- exercises/practice/hamming/hamming.ts | 3 + .../practice/hello-world/.meta/proof.ci.ts | 3 + .../hello-world/hello-world.example.ts | 7 - .../practice/hello-world/hello-world.test.ts | 4 +- exercises/practice/hello-world/hello-world.ts | 8 +- exercises/practice/house/.meta/proof.ci.ts | 64 + exercises/practice/house/house.example.ts | 68 -- exercises/practice/house/house.test.ts | 30 +- exercises/practice/house/house.ts | 7 + .../practice/isbn-verifier/.meta/proof.ci.ts | 18 + .../isbn-verifier/isbn-verifier.example.ts | 27 - .../isbn-verifier/isbn-verifier.test.ts | 30 +- .../practice/isbn-verifier/isbn-verifier.ts | 3 + exercises/practice/isogram/.meta/proof.ci.ts | 28 + exercises/practice/isogram/isogram.example.ts | 34 - exercises/practice/isogram/isogram.test.ts | 20 +- exercises/practice/isogram/isogram.ts | 8 +- .../kindergarten-garden/.meta/proof.ci.ts | 60 + .../kindergarten-garden.example.ts | 68 -- .../kindergarten-garden.test.ts | 206 +++- .../kindergarten-garden.ts | 41 + .../largest-series-product/.meta/proof.ci.ts | 28 + .../largest-series-product.example.ts | 52 - .../largest-series-product.test.ts | 92 +- .../largest-series-product.ts | 3 + exercises/practice/leap/.meta/proof.ci.ts | 3 + exercises/practice/leap/leap.example.ts | 5 - exercises/practice/leap/leap.test.ts | 46 +- exercises/practice/leap/leap.ts | 6 +- .../proof.ci.ts} | 2 +- .../practice/linked-list/linked-list.test.ts | 2 +- exercises/practice/linked-list/linked-list.ts | 25 + exercises/practice/list-ops/.meta/proof.ci.ts | 122 ++ .../practice/list-ops/list-ops.example.ts | 78 -- exercises/practice/list-ops/list-ops.test.ts | 77 +- exercises/practice/list-ops/list-ops.ts | 37 + exercises/practice/luhn/.meta/proof.ci.ts | 26 + exercises/practice/luhn/luhn.example.ts | 30 - exercises/practice/luhn/luhn.test.ts | 24 +- exercises/practice/luhn/luhn.ts | 3 + .../matching-brackets/.meta/proof.ci.ts | 22 + .../matching-brackets.example.ts | 50 - .../matching-brackets.test.ts | 92 +- .../matching-brackets/matching-brackets.ts | 3 + .../{matrix.example.ts => .meta/proof.ci.ts} | 4 +- exercises/practice/matrix/matrix.test.ts | 2 +- exercises/practice/matrix/matrix.ts | 16 +- .../proof.ci.ts} | 22 +- .../practice/minesweeper/minesweeper.test.ts | 34 +- exercises/practice/minesweeper/minesweeper.ts | 3 + .../{ => .meta}/nth-prime.example.ts | 14 +- .../practice/nth-prime/nth-prime.test.ts | 14 +- exercises/practice/nth-prime/nth-prime.ts | 3 + .../nucleotide-count/.meta/proof.ci.ts | 31 + .../nucleotide-count.example.ts | 35 - .../nucleotide-count/nucleotide-count.test.ts | 12 +- .../nucleotide-count/nucleotide-count.ts | 8 +- .../practice/ocr-numbers/.meta/proof.ci.ts | 60 + .../ocr-numbers/ocr-numbers.example.ts | 61 - .../practice/ocr-numbers/ocr-numbers.test.ts | 36 +- exercises/practice/ocr-numbers/ocr-numbers.ts | 4 +- .../proof.ci.ts} | 6 +- .../palindrome-products.test.ts | 146 ++- .../palindrome-products.ts | 18 + exercises/practice/pangram/.meta/proof.ci.ts | 22 + exercises/practice/pangram/pangram.example.ts | 32 - exercises/practice/pangram/pangram.test.ts | 59 +- exercises/practice/pangram/pangram.ts | 3 + .../proof.ci.ts} | 2 +- .../pascals-triangle/pascals-triangle.test.ts | 2 +- .../pascals-triangle/pascals-triangle.ts | 2 +- .../perfect-numbers/.meta/proof.ci.ts | 51 + .../perfect-numbers.example.ts | 55 - .../perfect-numbers/perfect-numbers.test.ts | 28 +- .../perfect-numbers/perfect-numbers.ts | 3 + .../practice/phone-number/.meta/proof.ci.ts | 40 + .../phone-number/phone-number.example.ts | 25 - .../phone-number/phone-number.test.ts | 132 ++- .../practice/phone-number/phone-number.ts | 3 + .../practice/pig-latin/.meta/proof.ci.ts | 18 + .../practice/pig-latin/pig-latin.example.ts | 22 - .../practice/pig-latin/pig-latin.test.ts | 40 +- exercises/practice/pig-latin/pig-latin.ts | 3 + .../proof.ci.ts} | 2 +- .../prime-factors/prime-factors.test.ts | 2 +- .../practice/prime-factors/prime-factors.ts | 3 + .../protein-translation/.meta/proof.ci.ts | 45 + .../protein-translation.example.ts | 61 - .../protein-translation.test.ts | 48 +- .../protein-translation.ts | 8 +- .../{proverb.example.ts => .meta/proof.ci.ts} | 4 +- exercises/practice/proverb/proverb.test.ts | 14 +- exercises/practice/proverb/proverb.ts | 3 + .../pythagorean-triplet/.meta/proof.ci.ts | 50 + .../pythagorean-triplet.example.ts | 69 -- .../pythagorean-triplet.test.ts | 81 +- .../pythagorean-triplet.ts | 19 + .../queen-attack/.docs/instructions.append.md | 19 + .../practice/queen-attack/.meta/proof.ci.ts | 83 ++ .../queen-attack/queen-attack.example.ts | 66 -- .../queen-attack/queen-attack.test.ts | 196 +++- .../practice/queen-attack/queen-attack.ts | 24 + .../practice/raindrops/.meta/proof.ci.ts | 15 + .../practice/raindrops/raindrops.example.ts | 17 - .../practice/raindrops/raindrops.test.ts | 37 +- exercises/practice/raindrops/raindrops.ts | 3 + .../proof.ci.ts} | 4 +- .../rational-numbers/rational-numbers.test.ts | 2 +- .../rational-numbers/rational-numbers.ts | 37 + .../{react.example.ts => .meta/proof.ci.ts} | 0 .../practice/rectangles/.meta/proof.ci.ts | 58 + .../practice/rectangles/rectangles.example.ts | 62 - .../practice/rectangles/rectangles.test.ts | 46 +- exercises/practice/rectangles/rectangles.ts | 3 + .../resistor-color-duo/.meta/proof.ci.ts | 19 + .../resistor-color-duo.example.ts | 40 - .../resistor-color-duo.test.ts | 33 +- .../resistor-color-duo/resistor-color-duo.ts | 9 +- .../proof.ci.ts} | 0 .../practice/reverse-string/.meta/proof.ci.ts | 3 + .../reverse-string/reverse-string.example.ts | 7 - .../reverse-string/reverse-string.test.ts | 12 +- .../practice/reverse-string/reverse-string.ts | 8 +- .../rna-transcription/.meta/proof.ci.ts | 17 + .../rna-transcription.example.ts | 20 - .../rna-transcription.test.ts | 22 +- .../rna-transcription/rna-transcription.ts | 8 +- .../proof.ci.ts} | 0 .../robot-simulator/.meta/proof.ci.ts | 89 ++ .../robot-simulator.example.ts | 78 -- .../robot-simulator/robot-simulator.test.ts | 320 +++-- .../robot-simulator/robot-simulator.ts | 26 + .../proof.ci.ts} | 27 +- .../roman-numerals/roman-numerals.test.ts | 119 +- .../practice/roman-numerals/roman-numerals.ts | 8 +- .../rotational-cipher/.meta/proof.ci.ts | 17 + .../rotational-cipher.example.ts | 19 - .../rotational-cipher.test.ts | 25 +- .../rotational-cipher/rotational-cipher.ts | 4 +- .../run-length-encoding/.meta/proof.ci.ts | 11 + .../run-length-encoding.example.ts | 15 - .../run-length-encoding.test.ts | 32 +- .../run-length-encoding.ts | 7 + .../practice/saddle-points/.meta/proof.ci.ts | 33 + .../saddle-points/saddle-points.example.ts | 37 - .../saddle-points/saddle-points.test.ts | 20 +- .../practice/saddle-points/saddle-points.ts | 8 +- .../say/{say.example.ts => .meta/proof.ci.ts} | 14 +- exercises/practice/say/say.test.ts | 35 +- exercises/practice/say/say.ts | 3 + .../proof.ci.ts} | 4 +- .../scrabble-score/scrabble-score.test.ts | 2 +- .../practice/scrabble-score/scrabble-score.ts | 3 + .../secret-handshake/.meta/proof.ci.ts | 15 + .../secret-handshake.example.ts | 42 - .../secret-handshake/secret-handshake.test.ts | 124 +- .../secret-handshake/secret-handshake.ts | 3 + exercises/practice/series/.meta/proof.ci.ts | 44 + exercises/practice/series/series.example.ts | 27 - exercises/practice/series/series.test.ts | 109 +- exercises/practice/series/series.ts | 9 + exercises/practice/sieve/.meta/proof.ci.ts | 33 + exercises/practice/sieve/sieve.example.ts | 37 - exercises/practice/sieve/sieve.test.ts | 12 +- exercises/practice/sieve/sieve.ts | 3 + .../proof.ci.ts} | 4 +- .../simple-cipher/simple-cipher.test.ts | 2 +- .../practice/simple-cipher/simple-cipher.ts | 8 +- .../practice/space-age/.meta/proof.ci.ts | 20 + .../practice/space-age/space-age.example.ts | 36 - .../practice/space-age/space-age.test.ts | 54 +- exercises/practice/space-age/space-age.ts | 3 + .../practice/spiral-matrix/.meta/proof.ci.ts | 29 + .../spiral-matrix/spiral-matrix.example.ts | 33 - .../spiral-matrix/spiral-matrix.test.ts | 14 +- .../practice/spiral-matrix/spiral-matrix.ts | 3 + .../{strain.example.ts => .meta/proof.ci.ts} | 0 exercises/practice/strain/strain.ts | 7 + .../{sublist.example.ts => .meta/proof.ci.ts} | 4 +- exercises/practice/sublist/sublist.test.ts | 2 +- exercises/practice/sublist/sublist.ts | 9 + .../sum-of-multiples/.meta/proof.ci.ts | 9 + .../sum-of-multiples.example.ts | 22 - .../sum-of-multiples/sum-of-multiples.test.ts | 68 +- .../sum-of-multiples/sum-of-multiples.ts | 3 + .../proof.ci.ts} | 4 +- .../practice/transpose/transpose.test.ts | 2 +- exercises/practice/transpose/transpose.ts | 6 +- exercises/practice/triangle/.meta/proof.ci.ts | 38 + .../practice/triangle/triangle.example.ts | 55 - exercises/practice/triangle/triangle.test.ts | 142 ++- exercises/practice/triangle/triangle.ts | 18 +- .../practice/twelve-days/.meta/proof.ci.ts | 50 + .../twelve-days/twelve-days.example.ts | 56 - .../practice/twelve-days/twelve-days.test.ts | 32 +- exercises/practice/twelve-days/twelve-days.ts | 8 +- .../proof.ci.ts} | 0 .../practice/two-bucket/two-bucket.test.ts | 77 +- exercises/practice/two-bucket/two-bucket.ts | 17 + .../{two-fer.example.ts => .meta/proof.ci.ts} | 0 .../.meta/proof.ci.ts | 50 + .../variable-length-quantity.example.ts | 56 - .../variable-length-quantity.test.ts | 54 +- .../variable-length-quantity.ts | 7 + .../practice/word-count/.meta/proof.ci.ts | 18 + .../practice/word-count/word-count.example.ts | 22 - .../practice/word-count/word-count.test.ts | 32 +- exercises/practice/word-count/word-count.ts | 3 + .../proof.ci.ts} | 2 +- .../practice/word-search/word-search.test.ts | 2 +- exercises/practice/word-search/word-search.ts | 6 +- exercises/practice/wordy/.meta/proof.ci.ts | 51 + exercises/practice/wordy/wordy.example.ts | 60 - exercises/practice/wordy/wordy.test.ts | 120 +- exercises/practice/wordy/wordy.ts | 3 + 321 files changed, 5364 insertions(+), 5385 deletions(-) rename exercises/practice/accumulate/{accumulate.example.ts => .meta/proof.ci.ts} (63%) create mode 100644 exercises/practice/acronym/.meta/proof.ci.ts delete mode 100644 exercises/practice/acronym/acronym.example.ts create mode 100644 exercises/practice/all-your-base/.meta/proof.ci.ts delete mode 100644 exercises/practice/all-your-base/all-your-base.example.ts rename exercises/practice/allergies/{allergies.example.ts => .meta/proof.ci.ts} (95%) create mode 100644 exercises/practice/alphametics/.meta/proof.ci.ts delete mode 100644 exercises/practice/alphametics/alphametics.example.ts rename exercises/practice/anagram/{anagram.example.ts => .meta/proof.ci.ts} (92%) create mode 100644 exercises/practice/armstrong-numbers/.meta/proof.ci.ts delete mode 100644 exercises/practice/armstrong-numbers/armstrong-numbers.example.ts create mode 100644 exercises/practice/atbash-cipher/.meta/proof.ci.ts delete mode 100644 exercises/practice/atbash-cipher/atbash-cipher.example.ts create mode 100644 exercises/practice/beer-song/.meta/proof.ci.ts delete mode 100644 exercises/practice/beer-song/beer-song.example.ts rename exercises/practice/binary-search-tree/{binary-search-tree.example.ts => .meta/proof.ci.ts} (94%) create mode 100644 exercises/practice/binary-search/.meta/proof.ci.ts delete mode 100644 exercises/practice/binary-search/binary-search.example.ts create mode 100644 exercises/practice/bob/.meta/proof.ci.ts delete mode 100644 exercises/practice/bob/bob.example.ts create mode 100644 exercises/practice/bowling/.meta/proof.ci.ts delete mode 100644 exercises/practice/bowling/bowling.example.ts rename exercises/practice/circular-buffer/{circular-buffer.example.ts => .meta/proof.ci.ts} (91%) rename exercises/practice/clock/{clock.example.ts => .meta/proof.ci.ts} (100%) create mode 100644 exercises/practice/collatz-conjecture/.meta/proof.ci.ts delete mode 100644 exercises/practice/collatz-conjecture/collatz-conjecture.example.ts rename exercises/practice/complex-numbers/{complex-numbers.example.ts => .meta/proof.ci.ts} (100%) rename exercises/practice/connect/{connect.example.ts => .meta/proof.ci.ts} (98%) create mode 100644 exercises/practice/crypto-square/.meta/proof.ci.ts delete mode 100644 exercises/practice/crypto-square/crypto-square.example.ts rename exercises/practice/custom-set/{custom-set.example.ts => .meta/proof.ci.ts} (97%) create mode 100644 exercises/practice/diamond/.meta/proof.ci.ts delete mode 100644 exercises/practice/diamond/diamond.example.ts rename exercises/practice/difference-of-squares/{difference-of-squares.example.ts => .meta/proof.ci.ts} (95%) create mode 100644 exercises/practice/diffie-hellman/.meta/proof.ci.ts delete mode 100644 exercises/practice/diffie-hellman/diffie-hellman.example.ts rename exercises/practice/etl/{etl.example.ts => .meta/proof.ci.ts} (86%) create mode 100644 exercises/practice/flatten-array/.meta/proof.ci.ts delete mode 100644 exercises/practice/flatten-array/flatten-array.example.ts create mode 100644 exercises/practice/food-chain/.meta/proof.ci.ts delete mode 100644 exercises/practice/food-chain/food-chain.example.ts rename exercises/practice/gigasecond/{gigasecond.example.ts => .meta/proof.ci.ts} (84%) rename exercises/practice/grade-school/{grade-school.example.ts => .meta/proof.ci.ts} (100%) create mode 100644 exercises/practice/grains/.meta/proof.ci.ts delete mode 100644 exercises/practice/grains/grains.example.ts create mode 100644 exercises/practice/hamming/.meta/proof.ci.ts delete mode 100644 exercises/practice/hamming/hamming.example.ts create mode 100644 exercises/practice/hello-world/.meta/proof.ci.ts delete mode 100644 exercises/practice/hello-world/hello-world.example.ts create mode 100644 exercises/practice/house/.meta/proof.ci.ts delete mode 100644 exercises/practice/house/house.example.ts create mode 100644 exercises/practice/isbn-verifier/.meta/proof.ci.ts delete mode 100644 exercises/practice/isbn-verifier/isbn-verifier.example.ts create mode 100644 exercises/practice/isogram/.meta/proof.ci.ts delete mode 100644 exercises/practice/isogram/isogram.example.ts create mode 100644 exercises/practice/kindergarten-garden/.meta/proof.ci.ts delete mode 100644 exercises/practice/kindergarten-garden/kindergarten-garden.example.ts create mode 100644 exercises/practice/largest-series-product/.meta/proof.ci.ts delete mode 100644 exercises/practice/largest-series-product/largest-series-product.example.ts create mode 100644 exercises/practice/leap/.meta/proof.ci.ts delete mode 100644 exercises/practice/leap/leap.example.ts rename exercises/practice/linked-list/{linked-list.example.ts => .meta/proof.ci.ts} (98%) create mode 100644 exercises/practice/list-ops/.meta/proof.ci.ts delete mode 100644 exercises/practice/list-ops/list-ops.example.ts create mode 100644 exercises/practice/luhn/.meta/proof.ci.ts delete mode 100644 exercises/practice/luhn/luhn.example.ts create mode 100644 exercises/practice/matching-brackets/.meta/proof.ci.ts delete mode 100644 exercises/practice/matching-brackets/matching-brackets.example.ts rename exercises/practice/matrix/{matrix.example.ts => .meta/proof.ci.ts} (95%) rename exercises/practice/minesweeper/{minesweeper.example.ts => .meta/proof.ci.ts} (73%) rename exercises/practice/nth-prime/{ => .meta}/nth-prime.example.ts (60%) create mode 100644 exercises/practice/nucleotide-count/.meta/proof.ci.ts delete mode 100644 exercises/practice/nucleotide-count/nucleotide-count.example.ts create mode 100644 exercises/practice/ocr-numbers/.meta/proof.ci.ts delete mode 100644 exercises/practice/ocr-numbers/ocr-numbers.example.ts rename exercises/practice/palindrome-products/{palindrome-products.example.ts => .meta/proof.ci.ts} (93%) create mode 100644 exercises/practice/pangram/.meta/proof.ci.ts delete mode 100644 exercises/practice/pangram/pangram.example.ts rename exercises/practice/pascals-triangle/{pascals-triangle.example.ts => .meta/proof.ci.ts} (95%) create mode 100644 exercises/practice/perfect-numbers/.meta/proof.ci.ts delete mode 100644 exercises/practice/perfect-numbers/perfect-numbers.example.ts create mode 100644 exercises/practice/phone-number/.meta/proof.ci.ts delete mode 100644 exercises/practice/phone-number/phone-number.example.ts create mode 100644 exercises/practice/pig-latin/.meta/proof.ci.ts delete mode 100644 exercises/practice/pig-latin/pig-latin.example.ts rename exercises/practice/prime-factors/{prime-factors.example.ts => .meta/proof.ci.ts} (78%) create mode 100644 exercises/practice/protein-translation/.meta/proof.ci.ts delete mode 100644 exercises/practice/protein-translation/protein-translation.example.ts rename exercises/practice/proverb/{proverb.example.ts => .meta/proof.ci.ts} (82%) create mode 100644 exercises/practice/pythagorean-triplet/.meta/proof.ci.ts delete mode 100644 exercises/practice/pythagorean-triplet/pythagorean-triplet.example.ts create mode 100644 exercises/practice/queen-attack/.docs/instructions.append.md create mode 100644 exercises/practice/queen-attack/.meta/proof.ci.ts delete mode 100644 exercises/practice/queen-attack/queen-attack.example.ts create mode 100644 exercises/practice/raindrops/.meta/proof.ci.ts delete mode 100644 exercises/practice/raindrops/raindrops.example.ts rename exercises/practice/rational-numbers/{rational-numbers.example.ts => .meta/proof.ci.ts} (98%) rename exercises/practice/react/{react.example.ts => .meta/proof.ci.ts} (100%) create mode 100644 exercises/practice/rectangles/.meta/proof.ci.ts delete mode 100644 exercises/practice/rectangles/rectangles.example.ts create mode 100644 exercises/practice/resistor-color-duo/.meta/proof.ci.ts delete mode 100644 exercises/practice/resistor-color-duo/resistor-color-duo.example.ts rename exercises/practice/resistor-color/{resistor-color.example.ts => .meta/proof.ci.ts} (100%) create mode 100644 exercises/practice/reverse-string/.meta/proof.ci.ts delete mode 100644 exercises/practice/reverse-string/reverse-string.example.ts create mode 100644 exercises/practice/rna-transcription/.meta/proof.ci.ts delete mode 100644 exercises/practice/rna-transcription/rna-transcription.example.ts rename exercises/practice/robot-name/{robot-name.example.ts => .meta/proof.ci.ts} (100%) create mode 100644 exercises/practice/robot-simulator/.meta/proof.ci.ts delete mode 100644 exercises/practice/robot-simulator/robot-simulator.example.ts rename exercises/practice/roman-numerals/{roman-numerals.example.ts => .meta/proof.ci.ts} (53%) create mode 100644 exercises/practice/rotational-cipher/.meta/proof.ci.ts delete mode 100644 exercises/practice/rotational-cipher/rotational-cipher.example.ts create mode 100644 exercises/practice/run-length-encoding/.meta/proof.ci.ts delete mode 100644 exercises/practice/run-length-encoding/run-length-encoding.example.ts create mode 100644 exercises/practice/saddle-points/.meta/proof.ci.ts delete mode 100644 exercises/practice/saddle-points/saddle-points.example.ts rename exercises/practice/say/{say.example.ts => .meta/proof.ci.ts} (89%) rename exercises/practice/scrabble-score/{scrabble-score.example.ts => .meta/proof.ci.ts} (86%) create mode 100644 exercises/practice/secret-handshake/.meta/proof.ci.ts delete mode 100644 exercises/practice/secret-handshake/secret-handshake.example.ts create mode 100644 exercises/practice/series/.meta/proof.ci.ts delete mode 100644 exercises/practice/series/series.example.ts create mode 100644 exercises/practice/sieve/.meta/proof.ci.ts delete mode 100644 exercises/practice/sieve/sieve.example.ts rename exercises/practice/simple-cipher/{simple-cipher.example.ts => .meta/proof.ci.ts} (96%) create mode 100644 exercises/practice/space-age/.meta/proof.ci.ts delete mode 100644 exercises/practice/space-age/space-age.example.ts create mode 100644 exercises/practice/spiral-matrix/.meta/proof.ci.ts delete mode 100644 exercises/practice/spiral-matrix/spiral-matrix.example.ts rename exercises/practice/strain/{strain.example.ts => .meta/proof.ci.ts} (100%) rename exercises/practice/sublist/{sublist.example.ts => .meta/proof.ci.ts} (95%) create mode 100644 exercises/practice/sum-of-multiples/.meta/proof.ci.ts delete mode 100644 exercises/practice/sum-of-multiples/sum-of-multiples.example.ts rename exercises/practice/transpose/{transpose.example.ts => .meta/proof.ci.ts} (86%) create mode 100644 exercises/practice/triangle/.meta/proof.ci.ts delete mode 100644 exercises/practice/triangle/triangle.example.ts create mode 100644 exercises/practice/twelve-days/.meta/proof.ci.ts delete mode 100644 exercises/practice/twelve-days/twelve-days.example.ts rename exercises/practice/two-bucket/{two-bucket.example.ts => .meta/proof.ci.ts} (100%) rename exercises/practice/two-fer/{two-fer.example.ts => .meta/proof.ci.ts} (100%) create mode 100644 exercises/practice/variable-length-quantity/.meta/proof.ci.ts delete mode 100644 exercises/practice/variable-length-quantity/variable-length-quantity.example.ts create mode 100644 exercises/practice/word-count/.meta/proof.ci.ts delete mode 100644 exercises/practice/word-count/word-count.example.ts rename exercises/practice/word-search/{word-search.example.ts => .meta/proof.ci.ts} (99%) create mode 100644 exercises/practice/wordy/.meta/proof.ci.ts delete mode 100644 exercises/practice/wordy/wordy.example.ts diff --git a/exercises/practice/accumulate/accumulate.example.ts b/exercises/practice/accumulate/.meta/proof.ci.ts similarity index 63% rename from exercises/practice/accumulate/accumulate.example.ts rename to exercises/practice/accumulate/.meta/proof.ci.ts index 9b9db7fff..bc9973207 100644 --- a/exercises/practice/accumulate/accumulate.example.ts +++ b/exercises/practice/accumulate/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default (list: T[], accumulator: (arg: T) => O): O[] => { +export function accumulate(list: T[], accumulator: (arg: T) => O): O[] { const out = [] let idx = -1 const end = list.length diff --git a/exercises/practice/accumulate/accumulate.test.ts b/exercises/practice/accumulate/accumulate.test.ts index b8e6765a2..bfb57731a 100644 --- a/exercises/practice/accumulate/accumulate.test.ts +++ b/exercises/practice/accumulate/accumulate.test.ts @@ -1,4 +1,4 @@ -import accumulate from './accumulate' +import { accumulate } from './accumulate' describe('accumulate()', () => { it('accumulation empty', () => { diff --git a/exercises/practice/accumulate/accumulate.ts b/exercises/practice/accumulate/accumulate.ts index e69de29bb..e71e74db9 100644 --- a/exercises/practice/accumulate/accumulate.ts +++ b/exercises/practice/accumulate/accumulate.ts @@ -0,0 +1,3 @@ +export function accumulate(list: unknown, accumulator: unknown): never { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/acronym/.meta/proof.ci.ts b/exercises/practice/acronym/.meta/proof.ci.ts new file mode 100644 index 000000000..e39d0a26f --- /dev/null +++ b/exercises/practice/acronym/.meta/proof.ci.ts @@ -0,0 +1,12 @@ +export function parse(phrase: string): string { + if (typeof phrase !== 'undefined' && phrase !== undefined) { + const match = phrase.match(/[A-Z]+[a-z]*|[a-z]+/g) + return !match + ? '' + : match.reduce( + (acronym: string, word: string) => (acronym += word[0].toUpperCase()), + '' + ) + } + return '' +} diff --git a/exercises/practice/acronym/acronym.example.ts b/exercises/practice/acronym/acronym.example.ts deleted file mode 100644 index 8301d3e6f..000000000 --- a/exercises/practice/acronym/acronym.example.ts +++ /dev/null @@ -1,15 +0,0 @@ -export default class Acronym { - public static parse(phrase: string): string { - if (typeof phrase !== 'undefined' && phrase !== undefined) { - const match = phrase.match(/[A-Z]+[a-z]*|[a-z]+/g) - return !match - ? '' - : match.reduce( - (acronym: string, word: string) => - (acronym += word[0].toUpperCase()), - '' - ) - } - return '' - } -} diff --git a/exercises/practice/acronym/acronym.test.ts b/exercises/practice/acronym/acronym.test.ts index 5be0ea955..96650db7a 100644 --- a/exercises/practice/acronym/acronym.test.ts +++ b/exercises/practice/acronym/acronym.test.ts @@ -1,29 +1,27 @@ -import Acronym from './acronym' +import { parse } from './acronym' describe('Acronym are produced from', () => { it('title cased phrases', () => { - expect(Acronym.parse('Portable Network Graphics')).toEqual('PNG') + expect(parse('Portable Network Graphics')).toEqual('PNG') }) xit('other title cased phrases', () => { - expect(Acronym.parse('Ruby on Rails')).toEqual('ROR') + expect(parse('Ruby on Rails')).toEqual('ROR') }) xit('inconsistently cased phrases', () => { - expect(Acronym.parse('HyperText Markup Language')).toEqual('HTML') + expect(parse('HyperText Markup Language')).toEqual('HTML') }) xit('phrases with punctuation', () => { - expect(Acronym.parse('First In, First Out')).toEqual('FIFO') + expect(parse('First In, First Out')).toEqual('FIFO') }) xit('other phrases with punctuation', () => { - expect(Acronym.parse('PHP: Hypertext Preprocessor')).toEqual('PHP') + expect(parse('PHP: Hypertext Preprocessor')).toEqual('PHP') }) xit('phrases with punctuation and sentence casing', () => { - expect(Acronym.parse('Complementary metal-oxide semiconductor')).toEqual( - 'CMOS' - ) + expect(parse('Complementary metal-oxide semiconductor')).toEqual('CMOS') }) }) diff --git a/exercises/practice/acronym/acronym.ts b/exercises/practice/acronym/acronym.ts index f17a8c4f2..64f71dfab 100644 --- a/exercises/practice/acronym/acronym.ts +++ b/exercises/practice/acronym/acronym.ts @@ -1,5 +1,3 @@ -export default class Acronym { - public static parse(phrase: string): string { - return '' - } +export function parse(phrase: unknown): unknown { + throw new Error('Remove this statement and implement this function') } diff --git a/exercises/practice/all-your-base/.meta/proof.ci.ts b/exercises/practice/all-your-base/.meta/proof.ci.ts new file mode 100644 index 000000000..f5e51bfe5 --- /dev/null +++ b/exercises/practice/all-your-base/.meta/proof.ci.ts @@ -0,0 +1,59 @@ +const isValidBase = (base: number): boolean => { + return !base || base < 2 || Math.floor(base) !== base +} + +const isInputValid = (array: number[], base: number): boolean => { + if (!array || !array.length) { + return false + } + const val = base - 1 + for (let i = 0, n = array.length; i < n; i++) { + const tmp = array[i] + if (tmp > val || tmp < 0) { + return false + } + } + return true +} + +const convertFromDecimalToBase = ( + num: number, + outputBase: number +): number[] => { + let tmp = num + const result = [] + while (tmp) { + result.unshift(tmp % outputBase) + tmp = Math.floor(tmp / outputBase) + } + return result +} + +export function convert( + array: number[], + inputBase: number, + outputBase: number +): number[] { + if (isValidBase(inputBase)) { + throw new Error('Wrong input base') + } + if (isValidBase(outputBase)) { + throw new Error('Wrong output base') + } + const regexp = new RegExp('^0.', 'g') + const str = array.join('') + if (str.match(regexp) || !isInputValid(array, inputBase)) { + throw new Error('Input has wrong format') + } + if (str === '0') { + return [0] + } + if (str === '1') { + return [1] + } + const decimalValue = array.reduce( + (accumulator, value) => accumulator * inputBase + value, + 0 + ) + return convertFromDecimalToBase(decimalValue, outputBase) +} diff --git a/exercises/practice/all-your-base/all-your-base.example.ts b/exercises/practice/all-your-base/all-your-base.example.ts deleted file mode 100644 index 5b5668286..000000000 --- a/exercises/practice/all-your-base/all-your-base.example.ts +++ /dev/null @@ -1,61 +0,0 @@ -const isValidBase = (base: number): boolean => { - return !base || base < 2 || Math.floor(base) !== base -} - -const isInputValid = (array: number[], base: number): boolean => { - if (!array || !array.length) { - return false - } - const val = base - 1 - for (let i = 0, n = array.length; i < n; i++) { - const tmp = array[i] - if (tmp > val || tmp < 0) { - return false - } - } - return true -} - -const convertFromDecimalToBase = ( - num: number, - outputBase: number -): number[] => { - let tmp = num - const result = [] - while (tmp) { - result.unshift(tmp % outputBase) - tmp = Math.floor(tmp / outputBase) - } - return result -} - -export default class Converter { - public convert( - array: number[], - inputBase: number, - outputBase: number - ): number[] { - if (isValidBase(inputBase)) { - throw new Error('Wrong input base') - } - if (isValidBase(outputBase)) { - throw new Error('Wrong output base') - } - const regexp = new RegExp('^0.', 'g') - const str = array.join('') - if (str.match(regexp) || !isInputValid(array, inputBase)) { - throw new Error('Input has wrong format') - } - if (str === '0') { - return [0] - } - if (str === '1') { - return [1] - } - const decimalValue = array.reduce( - (accumulator, value) => accumulator * inputBase + value, - 0 - ) - return convertFromDecimalToBase(decimalValue, outputBase) - } -} diff --git a/exercises/practice/all-your-base/all-your-base.test.ts b/exercises/practice/all-your-base/all-your-base.test.ts index 5919ca276..1745826fd 100644 --- a/exercises/practice/all-your-base/all-your-base.test.ts +++ b/exercises/practice/all-your-base/all-your-base.test.ts @@ -1,119 +1,117 @@ -import Converter from './all-your-base' - -const converter = new Converter() +import { convert } from './all-your-base' describe('Converter', () => { it('single bit one to decimal', () => { - expect(converter.convert([1], 2, 10)).toEqual([1]) + expect(convert([1], 2, 10)).toEqual([1]) }) xit('binary to single decimal', () => { - expect(converter.convert([1, 0, 1], 2, 10)).toEqual([5]) + expect(convert([1, 0, 1], 2, 10)).toEqual([5]) }) xit('single decimal to binary', () => { - expect(converter.convert([5], 10, 2)).toEqual([1, 0, 1]) + expect(convert([5], 10, 2)).toEqual([1, 0, 1]) }) xit('binary to multiple decimal', () => { - expect(converter.convert([1, 0, 1, 0, 1, 0], 2, 10)).toEqual([4, 2]) + expect(convert([1, 0, 1, 0, 1, 0], 2, 10)).toEqual([4, 2]) }) xit('decimal to binary', () => { - expect(converter.convert([4, 2], 10, 2)).toEqual([1, 0, 1, 0, 1, 0]) + expect(convert([4, 2], 10, 2)).toEqual([1, 0, 1, 0, 1, 0]) }) xit('trinary to hexadecimal', () => { - expect(converter.convert([1, 1, 2, 0], 3, 16)).toEqual([2, 10]) + expect(convert([1, 1, 2, 0], 3, 16)).toEqual([2, 10]) }) xit('hexadecimal to trinary', () => { - expect(converter.convert([2, 10], 16, 3)).toEqual([1, 1, 2, 0]) + expect(convert([2, 10], 16, 3)).toEqual([1, 1, 2, 0]) }) xit('15-bit integer', () => { - expect(converter.convert([3, 46, 60], 97, 73)).toEqual([6, 10, 45]) + expect(convert([3, 46, 60], 97, 73)).toEqual([6, 10, 45]) }) xit('empty list', () => { expect(() => { - converter.convert([], 2, 10) + convert([], 2, 10) }).toThrowError('Input has wrong format') }) xit('single zero', () => { - expect(converter.convert([0], 10, 2)).toEqual([0]) + expect(convert([0], 10, 2)).toEqual([0]) }) xit('multiple zeros', () => { expect(() => { - converter.convert([0, 0, 0], 10, 2) + convert([0, 0, 0], 10, 2) }).toThrowError('Input has wrong format') }) xit('leading zeros', () => { expect(() => { - converter.convert([0, 6, 0], 7, 10) + convert([0, 6, 0], 7, 10) }).toThrowError('Input has wrong format') }) xit('negative digit', () => { expect(() => { - converter.convert([1, -1, 1, 0, 1, 0], 2, 10) + convert([1, -1, 1, 0, 1, 0], 2, 10) }).toThrowError('Input has wrong format') }) xit('invalid positive digit', () => { expect(() => { - converter.convert([1, 2, 1, 0, 1, 0], 2, 10) + convert([1, 2, 1, 0, 1, 0], 2, 10) }).toThrowError('Input has wrong format') }) xit('first base is one', () => { expect(() => { - converter.convert([], 1, 10) + convert([], 1, 10) }).toThrowError('Wrong input base') }) xit('second base is one', () => { expect(() => { - converter.convert([1, 0, 1, 0, 1, 0], 2, 1) + convert([1, 0, 1, 0, 1, 0], 2, 1) }).toThrowError('Wrong output base') }) xit('first base is zero', () => { expect(() => { - converter.convert([], 0, 10) + convert([], 0, 10) }).toThrowError('Wrong input base') }) xit('second base is zero', () => { expect(() => { - converter.convert([7], 10, 0) + convert([7], 10, 0) }).toThrowError('Wrong output base') }) xit('first base is negative', () => { expect(() => { - converter.convert([1], -2, 10) + convert([1], -2, 10) }).toThrowError('Wrong input base') }) xit('second base is negative', () => { expect(() => { - converter.convert([1], 2, -7) + convert([1], 2, -7) }).toThrowError('Wrong output base') }) xit('both bases are negative', () => { expect(() => { - converter.convert([1], -2, -7) + convert([1], -2, -7) }).toThrowError('Wrong input base') }) xit('wrong output_base base not integer', () => { expect(() => { - converter.convert([0], 3, 2.5) + convert([0], 3, 2.5) }).toThrowError('Wrong output base') }) }) diff --git a/exercises/practice/all-your-base/all-your-base.ts b/exercises/practice/all-your-base/all-your-base.ts index e69de29bb..d982e483a 100644 --- a/exercises/practice/all-your-base/all-your-base.ts +++ b/exercises/practice/all-your-base/all-your-base.ts @@ -0,0 +1,7 @@ +export function convert( + digits: unknown, + inputBase: unknown, + outputBase: unknown +): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/allergies/allergies.example.ts b/exercises/practice/allergies/.meta/proof.ci.ts similarity index 95% rename from exercises/practice/allergies/allergies.example.ts rename to exercises/practice/allergies/.meta/proof.ci.ts index 8e48e1f23..f7b56918a 100644 --- a/exercises/practice/allergies/allergies.example.ts +++ b/exercises/practice/allergies/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -class Allergies { +export class Allergies { private allergenIndex: number private possibleAllergies = [ @@ -44,5 +44,3 @@ class Allergies { return allergicTo } } - -export default Allergies diff --git a/exercises/practice/allergies/allergies.test.ts b/exercises/practice/allergies/allergies.test.ts index 16ed3c456..0476e7591 100644 --- a/exercises/practice/allergies/allergies.test.ts +++ b/exercises/practice/allergies/allergies.test.ts @@ -1,4 +1,4 @@ -import Allergies from './allergies' +import { Allergies } from './allergies' describe('allergicTo', () => { it('no allergies means not allergic', () => { diff --git a/exercises/practice/allergies/allergies.ts b/exercises/practice/allergies/allergies.ts index e69de29bb..8da59d4e3 100644 --- a/exercises/practice/allergies/allergies.ts +++ b/exercises/practice/allergies/allergies.ts @@ -0,0 +1,13 @@ +export class Allergies { + constructor(allergenIndex: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public list(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public allergicTo(allergen: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/alphametics/.meta/proof.ci.ts b/exercises/practice/alphametics/.meta/proof.ci.ts new file mode 100644 index 000000000..031da350c --- /dev/null +++ b/exercises/practice/alphametics/.meta/proof.ci.ts @@ -0,0 +1,128 @@ +export function solve(puzzle: string): undefined | { [key: string]: number } { + const parts: string[] = puzzle + .split(/[+|==]/g) + .map((o) => o.trim()) + .filter((o) => o !== '') + + if (parts.length < 3) { + return undefined + } + + const uniqueLetters = new Set(parts.join('').split('')) + const firstLetters = new Set(parts.map((p) => p[0])) + + const numberCombinations: number[][] = getNumberCombinations( + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + uniqueLetters.size + ) + + while (numberCombinations.length) { + const permutations = generate( + Array(uniqueLetters.size) + .fill(Array()) + .map((_, i) => i) + ) + const numberCombination: number[] = numberCombinations.pop() || [] + for (const permutation of permutations) { + const newNumbers = assignNumbers( + numberCombination, + uniqueLetters, + permutation + ) + if (testNumbers(newNumbers, parts, firstLetters)) { + return newNumbers + } + } + } + return undefined +} + +function assignNumbers( + numberCombination: number[], + uniqueLetters: Set, + permutation: number[] +): { [key: string]: number } { + const output: { [key: string]: number } = {} + let i = 0 + for (const letter of uniqueLetters.values()) { + output[letter] = numberCombination[permutation[i++]] + } + return output +} + +function testNumbers( + numbers: { [key: string]: number }, + puzzleParts: string[], + firstLetters: Set +): boolean { + const keys: string[] = Object.keys(numbers) + for (const key of keys) { + if (numbers[key] === 0 && firstLetters.has(key)) { + return false + } + } + const replaceRegex = new RegExp(`[${keys.join('')}]`, 'g') + + const puzzlePartsNumbers: number[] = puzzleParts + .join(',') + .replace(replaceRegex, (input) => numbers[input].toString()) + .split(',') + .map((t) => parseInt(t, 10)) + + const total = puzzlePartsNumbers.slice(puzzlePartsNumbers.length - 1)[0] + return ( + total === + puzzlePartsNumbers + .slice(0, puzzleParts.length - 1) + .reduce((acc: number, val: number) => acc + val, 0) + ) +} + +function* generate(A: number[]): IterableIterator { + const c = [] + const n = A.length + yield A + for (let i = 0; i < n; i++) { + c[i] = 0 + } + let i = 0 + while (i < n) { + if (c[i] < i) { + if (i % 2 === 0) { + this.swap(A, 0, i) + } else { + this.swap(A, c[i], i) + } + yield A + c[i] += 1 + i = 0 + } else { + c[i] = 0 + i += 1 + } + } +} + +function swap(list: number[], x: number, y: number): number[] { + const tmp = list[x] + list[x] = list[y] + list[y] = tmp + return list +} + +function getNumberCombinations(arr: number[], size: number): number[][] { + const len = arr.length + + if (size === len) { + return [arr] + } + + return arr.reduce((acc: number[][], val: number, i: number) => { + const res: number[][] = this.getNumberCombinations( + arr.slice(i + 1), + size - 1 + ).map((comb) => [val].concat(comb)) + + return acc.concat(res) + }, []) +} diff --git a/exercises/practice/alphametics/alphametics.example.ts b/exercises/practice/alphametics/alphametics.example.ts deleted file mode 100644 index 375cb063b..000000000 --- a/exercises/practice/alphametics/alphametics.example.ts +++ /dev/null @@ -1,138 +0,0 @@ -class Alphametics { - private readonly puzzle: string - - constructor(puzzle: string) { - this.puzzle = puzzle - } - - public solve(): undefined | { [key: string]: number } { - const parts: string[] = this.puzzle - .split(/[+|==]/g) - .map((o) => o.trim()) - .filter((o) => o !== '') - - if (parts.length < 3) { - return undefined - } - - const uniqueLetters = new Set(parts.join('').split('')) - const firstLetters = new Set(parts.map((p) => p[0])) - - const numberCombinations: number[][] = this.getNumberCombinations( - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - uniqueLetters.size - ) - - while (numberCombinations.length) { - const permutations = this.generate( - Array(uniqueLetters.size) - .fill(Array()) - .map((_, i) => i) - ) - const numberCombination: number[] = numberCombinations.pop() || [] - for (const permutation of permutations) { - const newNumbers = this.assignNumbers( - numberCombination, - uniqueLetters, - permutation - ) - if (this.testNumbers(newNumbers, parts, firstLetters)) { - return newNumbers - } - } - } - return undefined - } - - private assignNumbers( - numberCombination: number[], - uniqueLetters: Set, - permutation: number[] - ): { [key: string]: number } { - const output: { [key: string]: number } = {} - let i = 0 - for (const letter of uniqueLetters.values()) { - output[letter] = numberCombination[permutation[i++]] - } - return output - } - - private testNumbers( - numbers: { [key: string]: number }, - puzzleParts: string[], - firstLetters: Set - ): boolean { - const keys: string[] = Object.keys(numbers) - for (const key of keys) { - if (numbers[key] === 0 && firstLetters.has(key)) { - return false - } - } - const replaceRegex = new RegExp(`[${keys.join('')}]`, 'g') - - const puzzlePartsNumbers: number[] = puzzleParts - .join(',') - .replace(replaceRegex, (input) => numbers[input].toString()) - .split(',') - .map((t) => parseInt(t, 10)) - - const total = puzzlePartsNumbers.slice(puzzlePartsNumbers.length - 1)[0] - return ( - total === - puzzlePartsNumbers - .slice(0, puzzleParts.length - 1) - .reduce((acc: number, val: number) => acc + val, 0) - ) - } - - private *generate(A: number[]): IterableIterator { - const c = [] - const n = A.length - yield A - for (let i = 0; i < n; i++) { - c[i] = 0 - } - let i = 0 - while (i < n) { - if (c[i] < i) { - if (i % 2 === 0) { - this.swap(A, 0, i) - } else { - this.swap(A, c[i], i) - } - yield A - c[i] += 1 - i = 0 - } else { - c[i] = 0 - i += 1 - } - } - } - - private swap(list: number[], x: number, y: number): number[] { - const tmp = list[x] - list[x] = list[y] - list[y] = tmp - return list - } - - private getNumberCombinations(arr: number[], size: number): number[][] { - const len = arr.length - - if (size === len) { - return [arr] - } - - return arr.reduce((acc: number[][], val: number, i: number) => { - const res: number[][] = this.getNumberCombinations( - arr.slice(i + 1), - size - 1 - ).map((comb) => [val].concat(comb)) - - return acc.concat(res) - }, []) - } -} - -export default Alphametics diff --git a/exercises/practice/alphametics/alphametics.test.ts b/exercises/practice/alphametics/alphametics.test.ts index beea586e0..005e244ad 100644 --- a/exercises/practice/alphametics/alphametics.test.ts +++ b/exercises/practice/alphametics/alphametics.test.ts @@ -1,4 +1,4 @@ -import Alphametics from './alphametics' +import { solve } from './alphametics' describe('Solve the alphametics puzzle', () => { it('puzzle with three letters', () => { @@ -8,17 +8,17 @@ describe('Solve the alphametics puzzle', () => { B: 9, L: 0, } - expect(new Alphametics(puzzle).solve()).toEqual(expected) + expect(solve(puzzle)).toEqual(expected) }) xit('solution must have unique value for each letter', () => { const puzzle = 'A == B' - expect(new Alphametics(puzzle).solve()).toBeUndefined() + expect(solve(puzzle)).toBeUndefined() }) xit('leading zero solution is invalid', () => { const puzzle = 'ACA + DD == BD' - expect(new Alphametics(puzzle).solve()).toBeUndefined() + expect(solve(puzzle)).toBeUndefined() }) xit('puzzle with four letters', () => { @@ -29,7 +29,7 @@ describe('Solve the alphametics puzzle', () => { M: 1, O: 0, } - expect(new Alphametics(puzzle).solve()).toEqual(expected) + expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with six letters', () => { @@ -42,7 +42,7 @@ describe('Solve the alphametics puzzle', () => { A: 0, E: 2, } - expect(new Alphametics(puzzle).solve()).toEqual(expected) + expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with seven letters', () => { @@ -56,7 +56,7 @@ describe('Solve the alphametics puzzle', () => { S: 9, T: 7, } - expect(new Alphametics(puzzle).solve()).toEqual(expected) + expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with eight letters', () => { @@ -71,7 +71,7 @@ describe('Solve the alphametics puzzle', () => { R: 8, Y: 2, } - expect(new Alphametics(puzzle).solve()).toEqual(expected) + expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with ten letters', () => { @@ -88,6 +88,6 @@ describe('Solve the alphametics puzzle', () => { S: 6, T: 9, } - expect(new Alphametics(puzzle).solve()).toEqual(expected) + expect(solve(puzzle)).toEqual(expected) }) }) diff --git a/exercises/practice/alphametics/alphametics.ts b/exercises/practice/alphametics/alphametics.ts index e69de29bb..923e6461c 100644 --- a/exercises/practice/alphametics/alphametics.ts +++ b/exercises/practice/alphametics/alphametics.ts @@ -0,0 +1,3 @@ +export function solve(puzzle: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/anagram/anagram.example.ts b/exercises/practice/anagram/.meta/proof.ci.ts similarity index 92% rename from exercises/practice/anagram/anagram.example.ts rename to exercises/practice/anagram/.meta/proof.ci.ts index 36216d5d5..5a0d78c93 100644 --- a/exercises/practice/anagram/anagram.example.ts +++ b/exercises/practice/anagram/.meta/proof.ci.ts @@ -2,7 +2,7 @@ function sort(input: string): string { return input.toString().toLowerCase().split('').sort().join('') } -class Anagram { +export class Anagram { private value: string constructor(input: string) { this.value = input @@ -21,5 +21,3 @@ class Anagram { return result } } - -export default Anagram diff --git a/exercises/practice/anagram/anagram.test.ts b/exercises/practice/anagram/anagram.test.ts index 83b2dbd74..194ffef79 100644 --- a/exercises/practice/anagram/anagram.test.ts +++ b/exercises/practice/anagram/anagram.test.ts @@ -1,46 +1,44 @@ -import Anagram from './anagram' +import { Anagram } from './anagram' describe('Anagram', () => { it('no matches', () => { const subject = new Anagram('diaper') - const matches = subject.matches(...['hello', 'world', 'zombies', 'pants']) + const matches = subject.matches('hello', 'world', 'zombies', 'pants') expect(matches).toEqual([]) }) xit('detects simple anagram', () => { const subject = new Anagram('ant') - const matches = subject.matches(...['tan', 'stand', 'at']) + const matches = subject.matches('tan', 'stand', 'at') expect(matches).toEqual(['tan']) }) xit('does not detect false positives', () => { const subject = new Anagram('galea') - const matches = subject.matches(...['eagle']) + const matches = subject.matches('eagle') expect(matches).toEqual([]) }) xit('detects multiple anagrams', () => { const subject = new Anagram('master') - const matches = subject.matches(...['stream', 'pigeon', 'maters']) + const matches = subject.matches('stream', 'pigeon', 'maters') expect(matches).toEqual(['stream', 'maters']) }) xit('does not detect anagram subsets', () => { const subject = new Anagram('good') - const matches = subject.matches(...['dog', 'goody']) + const matches = subject.matches('dog', 'goody') expect(matches).toEqual([]) }) xit('detects anagram', () => { const subject = new Anagram('listen') - const matches = subject.matches( - ...['enlists', 'google', 'inlets', 'banana'] - ) + const matches = subject.matches('enlists', 'google', 'inlets', 'banana') expect(matches).toEqual(['inlets']) }) @@ -48,7 +46,12 @@ describe('Anagram', () => { xit('detects multiple anagrams', () => { const subject = new Anagram('allergy') const matches = subject.matches( - ...['gallery', 'ballerina', 'regally', 'clergy', 'largely', 'leading'] + 'gallery', + 'ballerina', + 'regally', + 'clergy', + 'largely', + 'leading' ) expect(matches).toEqual(['gallery', 'regally', 'largely']) @@ -56,16 +59,14 @@ describe('Anagram', () => { xit('detects anagrams case-insensitively', () => { const subject = new Anagram('Orchestra') - const matches = subject.matches( - ...['cashregister', 'Carthorse', 'radishes'] - ) + const matches = subject.matches('cashregister', 'Carthorse', 'radishes') expect(matches).toEqual(['Carthorse']) }) xit('does not detect a word as its own anagram', () => { const subject = new Anagram('banana') - const matches = subject.matches(...['Banana']) + const matches = subject.matches('Banana') expect(matches).toEqual([]) }) diff --git a/exercises/practice/anagram/anagram.ts b/exercises/practice/anagram/anagram.ts index e69de29bb..db0be3b0b 100644 --- a/exercises/practice/anagram/anagram.ts +++ b/exercises/practice/anagram/anagram.ts @@ -0,0 +1,9 @@ +export class Anagram { + constructor(input: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public matches(...potentials: unknown[]): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/armstrong-numbers/.meta/proof.ci.ts b/exercises/practice/armstrong-numbers/.meta/proof.ci.ts new file mode 100644 index 000000000..b5cada515 --- /dev/null +++ b/exercises/practice/armstrong-numbers/.meta/proof.ci.ts @@ -0,0 +1,7 @@ +export function isArmstrongNumber(input: number): boolean { + const digits = String(input).split('') + const sum = digits.reduce((total, current) => { + return total + Math.pow(parseInt(current, 10), digits.length) + }, 0) + return sum === input +} diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers.example.ts b/exercises/practice/armstrong-numbers/armstrong-numbers.example.ts deleted file mode 100644 index 76397ede4..000000000 --- a/exercises/practice/armstrong-numbers/armstrong-numbers.example.ts +++ /dev/null @@ -1,11 +0,0 @@ -class ArmstrongNumbers { - public static isArmstrongNumber(input: number): boolean { - const digits = String(input).split('') - const sum = digits.reduce((total, current) => { - return total + Math.pow(parseInt(current, 10), digits.length) - }, 0) - return sum === input - } -} - -export default ArmstrongNumbers diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers.test.ts b/exercises/practice/armstrong-numbers/armstrong-numbers.test.ts index 6e7370673..22d60eb40 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers.test.ts +++ b/exercises/practice/armstrong-numbers/armstrong-numbers.test.ts @@ -1,35 +1,35 @@ -import ArmstrongNumbers from './armstrong-numbers' +import { isArmstrongNumber } from './armstrong-numbers' describe('Armstrong Numbers', () => { it('Single digit numbers are Armstrong numbers', () => { - expect(ArmstrongNumbers.isArmstrongNumber(5)).toBeTruthy() + expect(isArmstrongNumber(5)).toBeTruthy() }) xit('There are no 2 digit Armstrong numbers', () => { - expect(ArmstrongNumbers.isArmstrongNumber(10)).toBeFalsy() + expect(isArmstrongNumber(10)).toBeFalsy() }) xit('Three digit number that is an Armstrong number', () => { - expect(ArmstrongNumbers.isArmstrongNumber(153)).toBeTruthy() + expect(isArmstrongNumber(153)).toBeTruthy() }) xit('Three digit number that is not an Armstrong number', () => { - expect(ArmstrongNumbers.isArmstrongNumber(100)).toBeFalsy() + expect(isArmstrongNumber(100)).toBeFalsy() }) xit('Four digit number that is an Armstrong number', () => { - expect(ArmstrongNumbers.isArmstrongNumber(9474)).toBeTruthy() + expect(isArmstrongNumber(9474)).toBeTruthy() }) xit('Four digit number that is not an Armstrong number', () => { - expect(ArmstrongNumbers.isArmstrongNumber(9475)).toBeFalsy() + expect(isArmstrongNumber(9475)).toBeFalsy() }) xit('Seven digit number that is an Armstrong number', () => { - expect(ArmstrongNumbers.isArmstrongNumber(9926315)).toBeTruthy() + expect(isArmstrongNumber(9926315)).toBeTruthy() }) xit('Seven digit number that is not an Armstrong number', () => { - expect(ArmstrongNumbers.isArmstrongNumber(9926314)).toBeFalsy() + expect(isArmstrongNumber(9926314)).toBeFalsy() }) }) diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers.ts b/exercises/practice/armstrong-numbers/armstrong-numbers.ts index e69de29bb..82e7fa340 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers.ts +++ b/exercises/practice/armstrong-numbers/armstrong-numbers.ts @@ -0,0 +1,3 @@ +export function isArmstrongNumber(number: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/atbash-cipher/.meta/proof.ci.ts b/exercises/practice/atbash-cipher/.meta/proof.ci.ts new file mode 100644 index 000000000..3d90f795c --- /dev/null +++ b/exercises/practice/atbash-cipher/.meta/proof.ci.ts @@ -0,0 +1,31 @@ +const alphabet = 'abcdefghijklmnopqrstuvwxyz' +const numbers = '0123456789' + +export function encode(plainText: string): string { + const lowerCaseLettersOnly = plainText + .toLowerCase() + .split('') + .filter((char) => alphabet.includes(char) || numbers.includes(char)) + .join('') + + return decode(lowerCaseLettersOnly) + .split('') + .reduce((accumulator: string[], _, index, array) => { + if (index % 5 === 0) { + accumulator.push(array.slice(index, index + 5).join('')) + } + return accumulator + }, []) + .join(' ') +} + +export function decode(cipherText: string): string { + return cipherText + .split(' ') + .join('') + .split('') + .map((char) => + alphabet.includes(char) ? alphabet[25 - alphabet.indexOf(char)] : char + ) + .join('') +} diff --git a/exercises/practice/atbash-cipher/atbash-cipher.example.ts b/exercises/practice/atbash-cipher/atbash-cipher.example.ts deleted file mode 100644 index b1bc8f80f..000000000 --- a/exercises/practice/atbash-cipher/atbash-cipher.example.ts +++ /dev/null @@ -1,39 +0,0 @@ -class AtbashCipher { - private alphabet = 'abcdefghijklmnopqrstuvwxyz' - private numbers = '0123456789' - - public encode(plainText: string): string { - const lowerCaseLettersOnly = plainText - .toLowerCase() - .split('') - .filter( - (char) => this.alphabet.includes(char) || this.numbers.includes(char) - ) - .join('') - - return this.decode(lowerCaseLettersOnly) - .split('') - .reduce((accumulator: string[], _, index, array) => { - if (index % 5 === 0) { - accumulator.push(array.slice(index, index + 5).join('')) - } - return accumulator - }, []) - .join(' ') - } - - public decode(cipherText: string): string { - return cipherText - .split(' ') - .join('') - .split('') - .map((char) => - this.alphabet.includes(char) - ? this.alphabet[25 - this.alphabet.indexOf(char)] - : char - ) - .join('') - } -} - -export default AtbashCipher diff --git a/exercises/practice/atbash-cipher/atbash-cipher.test.ts b/exercises/practice/atbash-cipher/atbash-cipher.test.ts index 8a50e9d0c..4adefd62d 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher.test.ts +++ b/exercises/practice/atbash-cipher/atbash-cipher.test.ts @@ -1,74 +1,66 @@ -import AtbashCipher from './atbash-cipher' +import { encode, decode } from './atbash-cipher' describe('AtbashCipher', () => { - let atbash: AtbashCipher - - beforeAll(() => { - atbash = new AtbashCipher() - }) - describe('encoding', () => { it('encode yes', () => { - const cipherText = atbash.encode('yes') + const cipherText = encode('yes') expect(cipherText).toEqual('bvh') }) xit('encode no', () => { - const cipherText = atbash.encode('no') + const cipherText = encode('no') expect(cipherText).toEqual('ml') }) xit('encode OMG', () => { - const cipherText = atbash.encode('OMG') + const cipherText = encode('OMG') expect(cipherText).toEqual('lnt') }) xit('encode spaces', () => { - const cipherText = atbash.encode('O M G') + const cipherText = encode('O M G') expect(cipherText).toEqual('lnt') }) xit('encode mindblowingly', () => { - const cipherText = atbash.encode('mindblowingly') + const cipherText = encode('mindblowingly') expect(cipherText).toEqual('nrmwy oldrm tob') }) xit('encode numbers', () => { - const cipherText = atbash.encode('Testing,1 2 3, testing.') + const cipherText = encode('Testing,1 2 3, testing.') expect(cipherText).toEqual('gvhgr mt123 gvhgr mt') }) xit('encode deep thought', () => { - const cipherText = atbash.encode('Truth is fiction.') + const cipherText = encode('Truth is fiction.') expect(cipherText).toEqual('gifgs rhurx grlm') }) xit('encode all the letters', () => { - const cipherText = atbash.encode('thequickbrownfoxjumpsoverthelazydog') + const cipherText = encode('thequickbrownfoxjumpsoverthelazydog') expect(cipherText).toEqual('gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt') }) }) xdescribe('decode', () => { xit('decode exercism', () => { - const plainText = atbash.decode('vcvix rhn') + const plainText = decode('vcvix rhn') expect(plainText).toEqual('exercism') }) xit('decode a sentence', () => { - const cipherText = atbash.decode('zmlyh gzxov rhlug vmzhg vkkrm thglm v') + const cipherText = decode('zmlyh gzxov rhlug vmzhg vkkrm thglm v') expect(cipherText).toEqual('anobstacleisoftenasteppingstone') }) xit('decode numbers', () => { - const plainText = atbash.decode('gvhgr mt123 gvhgr mt') + const plainText = decode('gvhgr mt123 gvhgr mt') expect(plainText).toEqual('testing123testing') }) xit('decode all the letters', () => { - const cipherText = atbash.decode( - 'gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt' - ) + const cipherText = decode('gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt') expect(cipherText).toEqual('thequickbrownfoxjumpsoverthelazydog') }) }) diff --git a/exercises/practice/atbash-cipher/atbash-cipher.ts b/exercises/practice/atbash-cipher/atbash-cipher.ts index e69de29bb..5f2fb65a7 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher.ts +++ b/exercises/practice/atbash-cipher/atbash-cipher.ts @@ -0,0 +1,7 @@ +export function encode(plainText: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} + +export function decode(cipherText: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/beer-song/.meta/proof.ci.ts b/exercises/practice/beer-song/.meta/proof.ci.ts new file mode 100644 index 000000000..c38fde9c6 --- /dev/null +++ b/exercises/practice/beer-song/.meta/proof.ci.ts @@ -0,0 +1,35 @@ +function pluralize(input: number): string { + if (input === 0) { + return 'o more bottles ' + } + + return input === 1 ? '1 bottle ' : `${input} bottles ` +} + +export function verse(input: number): string { + const wall = 'of beer on the wall' + if (input === 0) { + return `N${pluralize(0)}${wall}, n${pluralize(0)}of beer. +Go to the store and buy some more, ${pluralize(99)}${wall}. +` + } + if (input === 1) { + return `${pluralize(1)}${wall}, ${pluralize(1)}of beer. +Take it down and pass it around, n${pluralize(0)}${wall}. +` + } + return `${pluralize(input)}${wall}, ${pluralize(input)}of beer. +Take one down and pass it around, ${pluralize(input - 1)}${wall}. +` +} + +export function sing(end: number = 99, start: number = 0): string { + let temp = `` + for (let i: number = end; i >= start; i -= 1) { + temp += verse(i) + if (i !== start) { + temp += '\n' + } + } + return temp +} diff --git a/exercises/practice/beer-song/beer-song.example.ts b/exercises/practice/beer-song/beer-song.example.ts deleted file mode 100644 index 4471dc297..000000000 --- a/exercises/practice/beer-song/beer-song.example.ts +++ /dev/null @@ -1,39 +0,0 @@ -class Beer { - private static pluralize(input: number): string { - if (input === 0) { - return 'o more bottles ' - } - - return input === 1 ? '1 bottle ' : `${input} bottles ` - } - - public static verse(input: number): string { - const wall = 'of beer on the wall' - if (input === 0) { - return `N${Beer.pluralize(0)}${wall}, n${Beer.pluralize(0)}of beer. -Go to the store and buy some more, ${Beer.pluralize(99)}${wall}. -` - } - if (input === 1) { - return `${Beer.pluralize(1)}${wall}, ${Beer.pluralize(1)}of beer. -Take it down and pass it around, n${Beer.pluralize(0)}${wall}. -` - } - return `${Beer.pluralize(input)}${wall}, ${Beer.pluralize(input)}of beer. -Take one down and pass it around, ${Beer.pluralize(input - 1)}${wall}. -` - } - - public static sing(end: number = 99, start: number = 0): string { - let temp = `` - for (let i: number = end; i >= start; i -= 1) { - temp += Beer.verse(i) - if (i !== start) { - temp += '\n' - } - } - return temp - } -} - -export default Beer diff --git a/exercises/practice/beer-song/beer-song.test.ts b/exercises/practice/beer-song/beer-song.test.ts index 359318436..fa2d942e7 100644 --- a/exercises/practice/beer-song/beer-song.test.ts +++ b/exercises/practice/beer-song/beer-song.test.ts @@ -1,4 +1,4 @@ -import Beer from './beer-song' +import { verse, sing } from './beer-song' describe('Beer', () => { it('prints an arbitrary verse', () => { @@ -6,21 +6,21 @@ describe('Beer', () => { Take one down and pass it around, 7 bottles of beer on the wall. ` - expect(Beer.verse(8)).toEqual(expected) + expect(verse(8)).toEqual(expected) }) xit('handles 1 bottle', () => { const expected = `1 bottle of beer on the wall, 1 bottle of beer. Take it down and pass it around, no more bottles of beer on the wall. ` - expect(Beer.verse(1)).toEqual(expected) + expect(verse(1)).toEqual(expected) }) xit('handles 0 bottles', () => { const expected = `No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ` - expect(Beer.verse(0)).toEqual(expected) + expect(verse(0)).toEqual(expected) }) xit('sings several verses', () => { @@ -33,7 +33,7 @@ Take one down and pass it around, 6 bottles of beer on the wall. 6 bottles of beer on the wall, 6 bottles of beer. Take one down and pass it around, 5 bottles of beer on the wall. ` - expect(Beer.sing(8, 6)).toEqual(expected) + expect(sing(8, 6)).toEqual(expected) }) xit('sings the rest of the verses', () => { @@ -49,11 +49,11 @@ Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ` - expect(Beer.sing(3)).toEqual(expected) + expect(sing(3)).toEqual(expected) }) xit('sings all the verses', () => { - const song = Beer.sing() + const song = sing() expect(song).toEqual(`99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall. diff --git a/exercises/practice/beer-song/beer-song.ts b/exercises/practice/beer-song/beer-song.ts index e69de29bb..16701a8f8 100644 --- a/exercises/practice/beer-song/beer-song.ts +++ b/exercises/practice/beer-song/beer-song.ts @@ -0,0 +1,10 @@ +export function verse(index: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} + +export function sing( + initialBottlesCount?: unknown, + takeDownCount?: unknown +): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/binary-search-tree/binary-search-tree.example.ts b/exercises/practice/binary-search-tree/.meta/proof.ci.ts similarity index 94% rename from exercises/practice/binary-search-tree/binary-search-tree.example.ts rename to exercises/practice/binary-search-tree/.meta/proof.ci.ts index 7d5ea4e9e..af2b5ead4 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree.example.ts +++ b/exercises/practice/binary-search-tree/.meta/proof.ci.ts @@ -1,7 +1,7 @@ type BinarySearchTreeMaybe = BinarySearchTree | undefined type EachFn = (data: number) => void -class BinarySearchTree { +export class BinarySearchTree { private readonly _data: number private _left?: BinarySearchTree private _right?: BinarySearchTree @@ -40,5 +40,3 @@ class BinarySearchTree { return this._right } } - -export default BinarySearchTree diff --git a/exercises/practice/binary-search-tree/binary-search-tree.test.ts b/exercises/practice/binary-search-tree/binary-search-tree.test.ts index 15f6a4d61..e0135309a 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree.test.ts +++ b/exercises/practice/binary-search-tree/binary-search-tree.test.ts @@ -1,8 +1,10 @@ -import BinarySearchTree from './binary-search-tree' +import { BinarySearchTree } from './binary-search-tree' -function recordAllData(bst: BinarySearchTree): number[] { - const out: number[] = [] - bst.each((data: number) => out.push(data)) +function recordAllData(bst: BinarySearchTree): unknown[] { + const out: unknown[] = [] + bst.each((data) => { + out.push(data) + }) return out } diff --git a/exercises/practice/binary-search-tree/binary-search-tree.ts b/exercises/practice/binary-search-tree/binary-search-tree.ts index e69de29bb..afca69768 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree.ts +++ b/exercises/practice/binary-search-tree/binary-search-tree.ts @@ -0,0 +1,25 @@ +export class BinarySearchTree { + constructor(data: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public get data(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public get right(): BinarySearchTree | undefined { + throw new Error('Remove this statement and implement this function') + } + + public get left(): BinarySearchTree | undefined { + throw new Error('Remove this statement and implement this function') + } + + public insert(item: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public each(callback: (data: unknown) => unknown): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/binary-search/.meta/proof.ci.ts b/exercises/practice/binary-search/.meta/proof.ci.ts new file mode 100644 index 000000000..42a3c97be --- /dev/null +++ b/exercises/practice/binary-search/.meta/proof.ci.ts @@ -0,0 +1,18 @@ +export function find(array: number[], element: number): number | never { + let start = 0 + let end = array.length - 1 + let middle: number + + while (start <= end) { + middle = Math.floor((start + end) / 2) + if (element === array[middle]) { + return middle + } else if (element < array[middle]) { + end = middle - 1 + } else if (element > array[middle]) { + start = middle + 1 + } + } + + throw new Error('Value not in array') +} diff --git a/exercises/practice/binary-search/binary-search.example.ts b/exercises/practice/binary-search/binary-search.example.ts deleted file mode 100644 index 7bf5235fc..000000000 --- a/exercises/practice/binary-search/binary-search.example.ts +++ /dev/null @@ -1,41 +0,0 @@ -export default class BinarySearch { - public readonly array!: T[] - - constructor(array: T[]) { - if (this.isSorted(array)) { - this.array = array - } - } - - public indexOf(val: T): ReturnType['search']> { - return this.search(val) - } - - private isSorted(array: T[]): boolean { - for (let i = 1; i < array.length; i++) { - if (array[i] < array[i - 1]) { - return false - } - } - return true - } - - private search(val: T): number { - const arr = this.array - let start = 0 - let end = arr.length - while (start <= end) { - const mid = Math.floor((start + end) / 2) - if (arr[mid] === val) { - return mid - } - if (arr[mid] > val) { - end = mid - 1 - } else { - start = mid + 1 - } - } - // val not found - return -1 - } -} diff --git a/exercises/practice/binary-search/binary-search.test.ts b/exercises/practice/binary-search/binary-search.test.ts index 729f26c7d..026743392 100644 --- a/exercises/practice/binary-search/binary-search.test.ts +++ b/exercises/practice/binary-search/binary-search.test.ts @@ -1,27 +1,55 @@ -import BinarySearch from './binary-search' +import { find } from './binary-search' -describe('BinarySearch', () => { - const sortedArray = [1, 2, 3, 4, 5, 6] - const sortedArrayOfOddLength = [0, 1, 2, 2, 3, 10, 12] - const unsortedArray = [10, 2, 5, 1] +describe('Binary Search', () => { + test('finds a value in an array with one element', () => { + expect(find([6], 6)).toEqual(0) + }) + + xtest('finds a value in the middle of an array', () => { + const array = [1, 3, 4, 6, 8, 9, 11] + expect(find(array, 6)).toEqual(3) + }) + + xtest('finds a value at the beginning of an array', () => { + const array = [1, 3, 4, 6, 8, 9, 11] + expect(find(array, 1)).toEqual(0) + }) + + xtest('finds a value at the end of an array', () => { + const array = [1, 3, 4, 6, 8, 9, 11] + expect(find(array, 11)).toEqual(6) + }) + + xtest('finds a value in an array of odd length', () => { + const array = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634] + expect(find(array, 144)).toEqual(9) + }) - it('should require a sorted array', () => { - const invalidBinarySearch = new BinarySearch(unsortedArray) - const validBinarySearch = new BinarySearch(sortedArray) + xtest('finds a value in an array of even length', () => { + const array = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377] + expect(find(array, 21)).toEqual(5) + }) + + xtest('identifies that a value is not included in the array', () => { + const array = [1, 3, 4, 6, 8, 9, 11] + expect(() => find(array, 7)).toThrow(new Error('Value not in array')) + }) - expect(typeof invalidBinarySearch.array).toEqual('undefined') - expect(Array.isArray(validBinarySearch.array)).toEqual(true) + xtest("a value smaller than the array's smallest value is not found", () => { + const array = [1, 3, 4, 6, 8, 9, 11] + expect(() => find(array, 0)).toThrow(new Error('Value not in array')) }) - xit('should find the correct index of an included value', () => { - expect(new BinarySearch(sortedArray).indexOf(3)).toEqual(2) + xtest("a value larger than the array's largest value is not found", () => { + const array = [1, 3, 4, 6, 8, 9, 11] + expect(() => find(array, 13)).toThrow(new Error('Value not in array')) }) - xit('should search the middle of the array', () => { - expect(new BinarySearch(sortedArrayOfOddLength).indexOf(2)).toEqual(3) + xtest('nothing is found in an empty array', () => { + expect(() => find([], 1)).toThrow(new Error('Value not in array')) }) - xit('should return -1 for a value not in the array', () => { - expect(new BinarySearch(sortedArray).indexOf(10)).toEqual(-1) + xtest('nothing is found when the left and right bounds cross', () => { + expect(() => find([1, 2], 0)).toThrow(new Error('Value not in array')) }) }) diff --git a/exercises/practice/binary-search/binary-search.ts b/exercises/practice/binary-search/binary-search.ts index 57264c5b0..70f7596e7 100644 --- a/exercises/practice/binary-search/binary-search.ts +++ b/exercises/practice/binary-search/binary-search.ts @@ -1 +1,3 @@ -export default class BinarySearch {} +export function find(haystack: unknown, needle: unknown): number | never { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/bob/.meta/proof.ci.ts b/exercises/practice/bob/.meta/proof.ci.ts new file mode 100644 index 000000000..9f50fa8ae --- /dev/null +++ b/exercises/practice/bob/.meta/proof.ci.ts @@ -0,0 +1,31 @@ +function isYelling(message: string): boolean { + return message.toUpperCase() === message && message.toLowerCase() !== message +} + +function isQuestion(message: string): boolean { + return message.trim().slice(-1) === '?' +} + +function isSilence(message: string): boolean { + return message.trim().length === 0 +} + +export function hey(message: string): string { + if (isYelling(message) && isQuestion(message)) { + return "Calm down, I know what I'm doing!" + } + + if (isYelling(message)) { + return 'Whoa, chill out!' + } + + if (isQuestion(message)) { + return 'Sure.' + } + + if (isSilence(message)) { + return 'Fine. Be that way!' + } + + return 'Whatever.' +} diff --git a/exercises/practice/bob/bob.example.ts b/exercises/practice/bob/bob.example.ts deleted file mode 100644 index 1f49b0b1a..000000000 --- a/exercises/practice/bob/bob.example.ts +++ /dev/null @@ -1,35 +0,0 @@ -class Bob { - private isYelling(message: string): boolean { - return ( - message.toUpperCase() === message && message.toLowerCase() !== message - ) - } - private isQuestion(message: string): boolean { - return message.trim().slice(-1) === '?' - } - private isSilence(message: string): boolean { - return message.trim().length === 0 - } - - public hey(message: string): string { - if (this.isYelling(message) && this.isQuestion(message)) { - return "Calm down, I know what I'm doing!" - } - - if (this.isYelling(message)) { - return 'Whoa, chill out!' - } - - if (this.isQuestion(message)) { - return 'Sure.' - } - - if (this.isSilence(message)) { - return 'Fine. Be that way!' - } - - return 'Whatever.' - } -} - -export default Bob diff --git a/exercises/practice/bob/bob.test.ts b/exercises/practice/bob/bob.test.ts index d4bfa8312..052ccb2e1 100644 --- a/exercises/practice/bob/bob.test.ts +++ b/exercises/practice/bob/bob.test.ts @@ -1,127 +1,123 @@ -import Bob from './bob' +import { hey } from './bob' describe('Bob', () => { - const bob = new Bob() - it('stating something', () => { - const result = bob.hey('Tom-ay-to, tom-aaaah-to.') + const result = hey('Tom-ay-to, tom-aaaah-to.') expect(result).toEqual('Whatever.') }) xit('shouting', () => { - const result = bob.hey('WATCH OUT!') + const result = hey('WATCH OUT!') expect(result).toEqual('Whoa, chill out!') }) xit('shouting gibberish', () => { - const result = bob.hey('FCECDFCAAB') + const result = hey('FCECDFCAAB') expect(result).toEqual('Whoa, chill out!') }) xit('asking a question', () => { - const result = bob.hey('Does this cryogenic chamber make me look fat?') + const result = hey('Does this cryogenic chamber make me look fat?') expect(result).toEqual('Sure.') }) xit('asking a numeric question', () => { - const result = bob.hey('You are, what, like 15?') + const result = hey('You are, what, like 15?') expect(result).toEqual('Sure.') }) xit('asking gibberish', () => { - const result = bob.hey('fffbbcbeab?') + const result = hey('fffbbcbeab?') expect(result).toEqual('Sure.') }) xit('talking forcefully', () => { - const result = bob.hey("Let's go make out behind the gym!") + const result = hey("Let's go make out behind the gym!") expect(result).toEqual('Whatever.') }) xit('using acronyms in regular speech', () => { - const result = bob.hey("It's OK if you don't want to go to the DMV.") + const result = hey("It's OK if you don't want to go to the DMV.") expect(result).toEqual('Whatever.') }) xit('forceful question', () => { - const result = bob.hey('WHAT THE HELL WERE YOU THINKING?') + const result = hey('WHAT THE HELL WERE YOU THINKING?') expect(result).toEqual("Calm down, I know what I'm doing!") }) xit('shouting numbers', () => { - const result = bob.hey('1, 2, 3 GO!') + const result = hey('1, 2, 3 GO!') expect(result).toEqual('Whoa, chill out!') }) xit('no letters', () => { - const result = bob.hey('1, 2, 3') + const result = hey('1, 2, 3') expect(result).toEqual('Whatever.') }) xit('question with no letters', () => { - const result = bob.hey('4?') + const result = hey('4?') expect(result).toEqual('Sure.') }) xit('shouting with special characters', () => { - const result = bob.hey('ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!') + const result = hey('ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!') expect(result).toEqual('Whoa, chill out!') }) xit('shouting with no exclamation mark', () => { - const result = bob.hey('I HATE THE DMV') + const result = hey('I HATE THE DMV') expect(result).toEqual('Whoa, chill out!') }) xit('statement containing question mark', () => { - const result = bob.hey('Ending with ? means a question.') + const result = hey('Ending with ? means a question.') expect(result).toEqual('Whatever.') }) xit('prattling on', () => { - const result = bob.hey('Wait! Hang on. Are you going to be OK?') + const result = hey('Wait! Hang on. Are you going to be OK?') expect(result).toEqual('Sure.') }) xit('silence', () => { - const result = bob.hey('') + const result = hey('') expect(result).toEqual('Fine. Be that way!') }) xit('prolonged silence', () => { - const result = bob.hey(' ') + const result = hey(' ') expect(result).toEqual('Fine. Be that way!') }) xit('alternate silence', () => { - const result = bob.hey('\t\t\t\t\t\t\t\t\t\t') + const result = hey('\t\t\t\t\t\t\t\t\t\t') expect(result).toEqual('Fine. Be that way!') }) xit('multiple line question', () => { - const result = bob.hey( - '\nDoes this cryogenic chamber make me look fat?\nNo.' - ) + const result = hey('\nDoes this cryogenic chamber make me look fat?\nNo.') expect(result).toEqual('Whatever.') }) xit('starting with whitespace', () => { - const result = bob.hey(' hmmmmmmm...') + const result = hey(' hmmmmmmm...') expect(result).toEqual('Whatever.') }) xit('ending with whitespace', () => { - const result = bob.hey('Okay if like my spacebar quite a bit? ') + const result = hey('Okay if like my spacebar quite a bit? ') expect(result).toEqual('Sure.') }) xit('other whitespace', () => { - const result = bob.hey('\n\r \t') + const result = hey('\n\r \t') expect(result).toEqual('Fine. Be that way!') }) xit('non-question ending with whitespace', () => { - const result = bob.hey('This is a statement ending with whitespace ') + const result = hey('This is a statement ending with whitespace ') expect(result).toEqual('Whatever.') }) }) diff --git a/exercises/practice/bob/bob.ts b/exercises/practice/bob/bob.ts index 4a3e6aa52..8b1e70145 100644 --- a/exercises/practice/bob/bob.ts +++ b/exercises/practice/bob/bob.ts @@ -1,7 +1,3 @@ -class Bob { - hey(/* Parameters go here */) { - // Your code here - } +export function hey(message: unknown): unknown { + throw new Error('Remove this statement and implement this function') } - -export default Bob diff --git a/exercises/practice/bowling/.meta/proof.ci.ts b/exercises/practice/bowling/.meta/proof.ci.ts new file mode 100644 index 000000000..bca3fa1b8 --- /dev/null +++ b/exercises/practice/bowling/.meta/proof.ci.ts @@ -0,0 +1,169 @@ +type Frame = 'X' | 'S' | number | undefined + +export class Bowling { + private readonly maxPins: number + private readonly maxFrames: number + + private currentFrame: number + private frames: Frame[] + private frameScores: number[] + private frameRoll: number + private remainingPins: number + + constructor() { + this.maxPins = 10 + this.maxFrames = 10 + this.currentFrame = 0 + + this.frames = [] + this.frameScores = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + this.initializeFrame() + } + + private initializeFrame(): void { + this.frameRoll = 1 + this.remainingPins = this.maxPins + this.currentFrame = this.currentFrame + 1 + } + + private incrementFrame(): void { + this.frameRoll = this.frameRoll + 1 + } + + private incrementScore(pins: number): void { + if (this.currentFrame > this.maxFrames) return + this.frameScores[this.currentFrame - 1] += pins + } + + private scoreStrike(): void { + this.frames[this.currentFrame - 1] = 'X' + this.applyStrikeBonus(this.maxPins) + this.applySpareBonus(this.maxPins) + this.incrementFrame() + } + + private scoreFirstRoll(pins: number): void { + this.remainingPins = this.remainingPins - pins + this.applySpareBonus(pins) + this.applyStrikeBonus(pins) + this.incrementFrame() + } + + private scoreSpare(pins: number): void { + this.frames[this.currentFrame - 1] = 'S' + this.applyStrikeBonus(pins) + this.incrementFrame() + } + + private scoreOpenFrame(pins: number): void { + this.frames[this.currentFrame - 1] = + this.maxPins - this.remainingPins + pins + this.applyStrikeBonus(pins) + this.incrementFrame() + } + + private applySpareBonus(pins: number): void { + // pins on the first roll after a spare are counted twice (on the frame of spare) + if (this.frames[this.currentFrame - 2] === 'S') { + this.frameScores[this.currentFrame - 2] += pins + } + } + + private applyStrikeBonus(pins: number): void { + // on the two rolls after a strike are counted twice (on the frame of the strike) + if ( + this.frames[this.currentFrame - 3] === 'X' && + this.frames[this.currentFrame - 2] === 'X' && + this.frameRoll === 1 && + this.currentFrame <= this.maxFrames + 2 + ) { + this.frameScores[this.currentFrame - 3] += pins + } + if ( + this.frames[this.currentFrame - 2] === 'X' && + this.currentFrame <= this.maxFrames + 1 + ) { + this.frameScores[this.currentFrame - 2] += pins + } + } + + private isGameOver(): boolean { + if (this.currentFrame <= this.maxFrames) return false + + if ( + this.frames[this.maxFrames - 1] !== 'X' && + this.frames[this.maxFrames - 1] !== 'S' + ) + return true + + // spare in the last frame gets no more than bonus roll + if (this.frames[this.maxFrames - 1] === 'S' && this.frameRoll > 1) + return true + + // bonus roll after the spare in the last frame may get a strike but then the games ends + // without another roll + if ( + this.frames[this.maxFrames - 1] === 'S' && + this.frames[this.maxFrames] === 'X' + ) + return true + + if (this.frames[this.maxFrames - 1] === 'X') { + // if the first bonus roll is not a strike then finish the bonus frame + if ( + this.frames[this.maxFrames] !== 'X' && + this.currentFrame > this.maxFrames + 1 + ) + return true + + if (this.frames[this.maxFrames] === 'X') { + // if the second bonus roll is a strike, but was still used, the game is over + if (this.frames[this.maxFrames + 1] !== 'X' && this.frameRoll > 1) + return true + // if the second bonus roll is a strike the game is over + if (this.frames[this.maxFrames + 1] === 'X') return true + } + } + return false + } + + public roll(pins: number): void { + if (pins < 0) { + throw new Error('Negative roll is invalid') + } + + if (pins > this.remainingPins) { + throw new Error('Pin count exceeds pins on the lane') + } + + if (this.isGameOver()) { + throw new Error('Cannot roll after game is over') + } + + this.incrementScore(pins) + + if (this.frameRoll === 1) { + if (pins === this.maxPins) { + this.scoreStrike() + this.initializeFrame() + } else { + this.scoreFirstRoll(pins) + } + } else { + if (pins === this.remainingPins) { + this.scoreSpare(pins) + } else { + this.scoreOpenFrame(pins) + } + this.initializeFrame() + } + } + + public score(): number { + if (!this.isGameOver()) { + throw new Error('Score cannot be taken until the end of the game') + } + return this.frameScores.reduce((total, num) => total + num) + } +} diff --git a/exercises/practice/bowling/bowling.example.ts b/exercises/practice/bowling/bowling.example.ts deleted file mode 100644 index 0ff8b7f0a..000000000 --- a/exercises/practice/bowling/bowling.example.ts +++ /dev/null @@ -1,90 +0,0 @@ -export default class Bowling { - private readonly rolls: number[] - - constructor(rolls: number[]) { - this.rolls = rolls - } - - public score(): number { - const initialState = { - frameNumber: 1, - rollNumber: 1, - pinsRemaining: 10, - spareLastFrame: false, - strikeLastFrame: false, - twoStrikesInARow: false, - fillBall: false, - score: 0, - } - - const finalState = this.rolls.reduce((state, roll) => { - if (roll < 0 || roll > 10) { - throw new Error('Pins must have a value from 0 to 10') - } - - if (roll > state.pinsRemaining) { - throw new Error('Pin count exceeds pins on the lane') - } - - if (state.frameNumber > 10) { - throw new Error('Should not be able to roll after game is over') - } - - const finalFrame = state.frameNumber === 10 - const strike = state.rollNumber === 1 && roll === 10 - const spare = state.rollNumber === 2 && roll === state.pinsRemaining - const frameOver = finalFrame - ? (!state.fillBall && !spare && state.rollNumber === 2) || - state.rollNumber === 3 - : strike || spare || state.rollNumber === 2 - - let score = state.score + roll - - if (state.strikeLastFrame && state.rollNumber < 3) { - score += roll - } - if (state.spareLastFrame && state.rollNumber === 1) { - score += roll - } - if (state.twoStrikesInARow && state.rollNumber === 1) { - score += roll - } - - const next = { - frameNumber: 0, - rollNumber: 0, - pinsRemaining: 0, - spareLastFrame: false, - strikeLastFrame: false, - twoStrikesInARow: false, - fillBall: false, - score: 0, - } - - next.frameNumber = frameOver ? state.frameNumber + 1 : state.frameNumber - next.rollNumber = frameOver ? 1 : state.rollNumber + 1 - next.pinsRemaining = finalFrame - ? strike || spare - ? 10 - : state.pinsRemaining - roll - : frameOver - ? 10 - : state.pinsRemaining - roll - next.spareLastFrame = frameOver ? spare : state.spareLastFrame - next.strikeLastFrame = frameOver ? strike : state.strikeLastFrame - next.twoStrikesInARow = frameOver - ? strike && state.strikeLastFrame - : state.twoStrikesInARow - next.fillBall = next.fillBall || (finalFrame && (strike || spare)) - next.score = score - - return next - }, initialState) - - if (finalState.frameNumber !== 11) { - throw new Error('Score cannot be taken until the end of the game') - } - - return finalState.score - } -} diff --git a/exercises/practice/bowling/bowling.test.ts b/exercises/practice/bowling/bowling.test.ts index 076984c06..ee5fe9309 100644 --- a/exercises/practice/bowling/bowling.test.ts +++ b/exercises/practice/bowling/bowling.test.ts @@ -1,33 +1,53 @@ -import Bowling from './bowling' +import { Bowling } from './bowling' describe('Bowling', () => { describe('Check game can be scored correctly.', () => { - it('should be able to score a game with all gutterballs', () => { + test('should be able to score a game with all zeros', () => { const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(0) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(0) }) - xit('should be able to score a game with all open frames', () => { + xtest('should be able to score a game with no strikes or spares', () => { const rolls = [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] - expect(new Bowling(rolls).score()).toEqual(90) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(90) }) - xit('a spare followed by zeros is worth ten points', () => { + xtest('a spare followed by zeros is worth ten points', () => { const rolls = [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(10) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(10) }) - xit('points scored in the roll after a spare are counted twice', () => { + xtest('points scored in the roll after a spare are counted twice', () => { const rolls = [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(16) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(16) }) - xit('consecutive spares each get a one roll bonus', () => { + xtest('consecutive spares each get a one roll bonus', () => { const rolls = [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(31) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(31) }) - xit('should allow fill ball when the last frame is a spare', () => { + xtest('a spare in the last frame gets a one roll bonus that is counted once', () => { const rolls = [ 0, 0, @@ -51,25 +71,41 @@ describe('Bowling', () => { 3, 7, ] - expect(new Bowling(rolls).score()).toEqual(17) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(17) }) - xit('a strike earns ten points in a frame with a single roll', () => { + xtest('a strike earns ten points in a frame with a single roll', () => { const rolls = [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(10) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(10) }) - xit('points scored in the two rolls after a strike are counted twice as a bonus', () => { + xtest('points scored in the two rolls after a strike are counted twice as a bonus', () => { const rolls = [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(26) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(26) }) - xit('should be able to score multiple strikes in a row', () => { + xtest('consecutive strikes each get the two roll bonus', () => { const rolls = [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(new Bowling(rolls).score()).toEqual(81) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(81) }) - xit('should allow fill balls when the last frame is a strike', () => { + xtest('a strike in the last frame gets a two roll bonues that is counted once', () => { const rolls = [ 0, 0, @@ -93,10 +129,14 @@ describe('Bowling', () => { 7, 1, ] - expect(new Bowling(rolls).score()).toEqual(18) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(18) }) - xit('rolling a spare with the two roll bonus does not get a bonus roll', () => { + xtest('rolling a spare with the two roll bonus does not get a bonus roll', () => { const rolls = [ 0, 0, @@ -120,10 +160,14 @@ describe('Bowling', () => { 7, 3, ] - expect(new Bowling(rolls).score()).toEqual(20) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(20) }) - xit('strikes with the two roll bonus do not get bonus rolls', () => { + xtest('strikes with the two roll bonus do not get bonus rolls', () => { const rolls = [ 0, 0, @@ -147,10 +191,14 @@ describe('Bowling', () => { 10, 10, ] - expect(new Bowling(rolls).score()).toEqual(30) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(30) }) - xit('a strike with the one roll bonus after a spare in the last frame does not get a bonus', () => { + xtest('a strike with the one roll bonus after a spare in the last frame does not get a bonus', () => { const rolls = [ 0, 0, @@ -174,20 +222,59 @@ describe('Bowling', () => { 3, 10, ] - expect(new Bowling(rolls).score()).toEqual(20) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(20) }) - xit('should be able to score a perfect game', () => { + xtest('all strikes is a perfect game', () => { const rolls = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] - expect(new Bowling(rolls).score()).toEqual(300) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(300) }) }) describe('Check game rules.', () => { - xit('rolls can not score negative points', () => { + xtest('rolls cannot score negative points', () => { + const bowling = new Bowling() + expect(() => { + bowling.roll(-1) + }).toThrow(new Error('Negative roll is invalid')) + }) + + xtest('a roll cannot score more than 10 points', () => { + const bowling = new Bowling() + expect(() => { + bowling.roll(11) + }).toThrow(new Error('Pin count exceeds pins on the lane')) + }) + + xtest('two rolls in a frame cannot score more than 10 points', () => { + const bowling = new Bowling() + bowling.roll(5) + expect(() => { + bowling.roll(6) + }).toThrow(new Error('Pin count exceeds pins on the lane')) + }) + + xtest('bonus roll after a strike in the last frame cannot score more than 10 points', () => { + const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(() => { + bowling.roll(11) + }).toThrow(new Error('Pin count exceeds pins on the lane')) + }) + + xtest('two bonus rolls after a strike in the last frame cannot score more than 10 points', () => { const rolls = [ - -1, - 0, 0, 0, 0, @@ -206,16 +293,20 @@ describe('Bowling', () => { 0, 0, 0, + 10, + 5, ] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) expect(() => { - new Bowling(rolls).score() - }).toThrowError('Pins must have a value from 0 to 10') + bowling.roll(6) + }).toThrow(new Error('Pin count exceeds pins on the lane')) }) - xit('a roll can not score more than 10 points', () => { + xtest('two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike', () => { const rolls = [ - 11, - 0, 0, 0, 0, @@ -234,20 +325,18 @@ describe('Bowling', () => { 0, 0, 0, + 10, + 10, + 6, ] - expect(() => { - new Bowling(rolls).score() - }).toThrowError('Pins must have a value from 0 to 10') - }) - - xit('two rolls in a frame can not score more than 10 points', () => { - const rolls = [5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - expect(() => { - new Bowling(rolls).score() - }).toThrowError('Pin count exceeds pins on the lane') + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(bowling.score()).toEqual(26) }) - xit('two bonus rolls after a strike in the last frame can not score more than 10 points', () => { + xtest('the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike', () => { const rolls = [ 0, 0, @@ -268,15 +357,18 @@ describe('Bowling', () => { 0, 0, 10, - 5, 6, ] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) expect(() => { - new Bowling(rolls).score() - }).toThrowError('Pin count exceeds pins on the lane') + bowling.roll(10) + }).toThrow(new Error('Pin count exceeds pins on the lane')) }) - xit('two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike', () => { + xtest('second bonus roll after a strike in the last frame cannot score more than 10 points', () => { const rolls = [ 0, 0, @@ -298,12 +390,57 @@ describe('Bowling', () => { 0, 10, 10, - 6, ] - expect(new Bowling(rolls).score()).toEqual(26) + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(() => { + bowling.roll(11) + }).toThrow(new Error('Pin count exceeds pins on the lane')) + }) + + xtest('an unstarted game cannot be scored', () => { + const bowling = new Bowling() + expect(() => { + bowling.score() + }).toThrow(new Error('Score cannot be taken until the end of the game')) }) - xit('the second bonus rolls after a strike in the last frame can not be a strike if the first one is not a strike', () => { + xtest('an incomplete game cannot be scored', () => { + const rolls = [0, 0] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(() => { + bowling.score() + }).toThrow(new Error('Score cannot be taken until the end of the game')) + }) + + xtest('cannot roll if game already has ten frames', () => { + const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(() => { + bowling.roll(0) + }).toThrow(new Error('Cannot roll after game is over')) + }) + + xtest('bonus rolls for a strike in the last frame must be rolled before score can be calculated', () => { + const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) + expect(() => { + bowling.score() + }).toThrow(new Error('Score cannot be taken until the end of the game')) + }) + + xtest('both bonus rolls for a strike in the last frame must be rolled before score can be calculated', () => { const rolls = [ 0, 0, @@ -324,29 +461,29 @@ describe('Bowling', () => { 0, 0, 10, - 6, 10, ] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) expect(() => { - new Bowling(rolls).score() - }).toThrowError('Pin count exceeds pins on the lane') - }) - - xit('an unstarted game can not be scored', () => { - const rolls: number[] = [] - expect(() => { - new Bowling(rolls).score() - }).toThrowError('Score cannot be taken until the end of the game') + bowling.score() + }).toThrow(new Error('Score cannot be taken until the end of the game')) }) - xit('an incomplete game can not be scored', () => { - const rolls = [0, 0] + xtest('bonus roll for a spare in the last frame must be rolled before score can be calculated', () => { + const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) expect(() => { - new Bowling(rolls).score() - }).toThrowError('Score cannot be taken until the end of the game') + bowling.score() + }).toThrow(new Error('Score cannot be taken until the end of the game')) }) - xit('a game with more than ten frames and no last frame spare or strike can not be scored', () => { + xtest('cannot roll after bonus roll for spare', () => { const rolls = [ 0, 0, @@ -366,23 +503,20 @@ describe('Bowling', () => { 0, 0, 0, - 0, - 0, - 0, + 7, + 3, + 2, ] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) expect(() => { - new Bowling(rolls).score() - }).toThrowError('Should not be able to roll after game is over') + bowling.roll(2) + }).toThrow(new Error('Cannot roll after game is over')) }) - xit('bonus rolls for a strike in the last frame must be rolled before score can be calculated', () => { - const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] - expect(() => { - new Bowling(rolls).score() - }).toThrowError('Score cannot be taken until the end of the game') - }) - - xit('both bonus rolls for a strike in the last frame must be rolled before score can be calculated', () => { + xtest('cannot roll after bonus rolls for strike', () => { const rolls = [ 0, 0, @@ -403,18 +537,16 @@ describe('Bowling', () => { 0, 0, 10, - 10, + 3, + 2, ] + const bowling = new Bowling() + rolls.forEach((roll) => { + bowling.roll(roll) + }) expect(() => { - new Bowling(rolls).score() - }).toThrowError('Score cannot be taken until the end of the game') - }) - - xit('bonus roll for a spare in the last frame must be rolled before score can be calculated', () => { - const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] - expect(() => { - new Bowling(rolls).score() - }).toThrowError('Score cannot be taken until the end of the game') + bowling.roll(2) + }).toThrow(new Error('Cannot roll after game is over')) }) }) }) diff --git a/exercises/practice/bowling/bowling.ts b/exercises/practice/bowling/bowling.ts index e69de29bb..9bd2d7a16 100644 --- a/exercises/practice/bowling/bowling.ts +++ b/exercises/practice/bowling/bowling.ts @@ -0,0 +1,9 @@ +export class Bowling { + public roll(pins: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public score(): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/circular-buffer/circular-buffer.example.ts b/exercises/practice/circular-buffer/.meta/proof.ci.ts similarity index 91% rename from exercises/practice/circular-buffer/circular-buffer.example.ts rename to exercises/practice/circular-buffer/.meta/proof.ci.ts index 54343b878..a2202969e 100644 --- a/exercises/practice/circular-buffer/circular-buffer.example.ts +++ b/exercises/practice/circular-buffer/.meta/proof.ci.ts @@ -4,7 +4,7 @@ interface Buffer { clear(): void } -export class BufferOverflowError extends Error { +export class BufferFullError extends Error { constructor() { super('Buffer is full.') } @@ -33,7 +33,7 @@ export default class CircularBuffer implements Buffer { public write(value: T): void { if (this.buffer.length + 1 > this.capacity) { - throw new BufferOverflowError() + throw new BufferFullError() } this.buffer.push(value) } diff --git a/exercises/practice/circular-buffer/circular-buffer.test.ts b/exercises/practice/circular-buffer/circular-buffer.test.ts index 4afa77f14..e41924ec6 100644 --- a/exercises/practice/circular-buffer/circular-buffer.test.ts +++ b/exercises/practice/circular-buffer/circular-buffer.test.ts @@ -1,5 +1,5 @@ import CircularBuffer, { - BufferOverflowError, + BufferFullError, BufferEmptyError, } from './circular-buffer' @@ -55,11 +55,11 @@ describe('CircularBuffer', () => { expect(buffer.read()).toBe('3') }) - it('writing to a full buffer throws a BufferOverflowError', () => { + it('writing to a full buffer throws a BufferFullError', () => { const buffer = new CircularBuffer(2) buffer.write('1') buffer.write('2') - expect(() => buffer.write('A')).toThrow(BufferOverflowError) + expect(() => buffer.write('A')).toThrow(BufferFullError) }) it('forced writes over write oldest item in a full buffer', () => { diff --git a/exercises/practice/circular-buffer/circular-buffer.ts b/exercises/practice/circular-buffer/circular-buffer.ts index e69de29bb..4e4e5442a 100644 --- a/exercises/practice/circular-buffer/circular-buffer.ts +++ b/exercises/practice/circular-buffer/circular-buffer.ts @@ -0,0 +1,35 @@ +export default class CircularBuffer { + constructor(initial: unknown) { + throw new Error('Remove this statement and implement this function') + } + + write(value: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + read(): unknown { + throw new Error('Remove this statement and implement this function') + } + + forceWrite(value: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + clear(): unknown { + throw new Error('Remove this statement and implement this function') + } +} + +export class BufferFullError extends Error { + constructor() { + super() + throw new Error('Remove this statement and implement this function') + } +} + +export class BufferEmptyError extends Error { + constructor() { + super() + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/clock/clock.example.ts b/exercises/practice/clock/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/clock/clock.example.ts rename to exercises/practice/clock/.meta/proof.ci.ts diff --git a/exercises/practice/clock/clock.test.ts b/exercises/practice/clock/clock.test.ts index ca95c2f92..0eff221a5 100644 --- a/exercises/practice/clock/clock.test.ts +++ b/exercises/practice/clock/clock.test.ts @@ -1,4 +1,4 @@ -import Clock from './clock' +import { Clock } from './clock' describe('Clock', () => { describe('Creating a new clock with an initial time', () => { diff --git a/exercises/practice/clock/clock.ts b/exercises/practice/clock/clock.ts index e69de29bb..0c3d79128 100644 --- a/exercises/practice/clock/clock.ts +++ b/exercises/practice/clock/clock.ts @@ -0,0 +1,21 @@ +export class Clock { + constructor(hour: unknown, minute?: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public toString(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public plus(minutes: unknown): Clock { + throw new Error('Remove this statement and implement this function') + } + + public minus(minutes: unknown): Clock { + throw new Error('Remove this statement and implement this function') + } + + public equals(other: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/collatz-conjecture/.meta/proof.ci.ts b/exercises/practice/collatz-conjecture/.meta/proof.ci.ts new file mode 100644 index 000000000..c13c9691e --- /dev/null +++ b/exercises/practice/collatz-conjecture/.meta/proof.ci.ts @@ -0,0 +1,16 @@ +export function steps(n: number): number { + if (n <= 0) { + throw new Error('Only positive numbers are allowed') + } + return calculateStepsRecursively(n, 0) +} + +function calculateStepsRecursively(n: number, count: number): number { + if (n === 1) { + return count + } else if (n % 2 === 0) { + return this.calculateStepsRecursively(n / 2, ++count) + } else { + return this.calculateStepsRecursively(n * 3 + 1, ++count) + } +} diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture.example.ts b/exercises/practice/collatz-conjecture/collatz-conjecture.example.ts deleted file mode 100644 index 1185a1983..000000000 --- a/exercises/practice/collatz-conjecture/collatz-conjecture.example.ts +++ /dev/null @@ -1,20 +0,0 @@ -class CollatzConjecture { - public static steps(n: number): number { - if (n <= 0) { - throw new Error('Only positive numbers are allowed') - } - return this.calculateStepsRecursively(n, 0) - } - - private static calculateStepsRecursively(n: number, count: number): number { - if (n === 1) { - return count - } else if (n % 2 === 0) { - return this.calculateStepsRecursively(n / 2, ++count) - } else { - return this.calculateStepsRecursively(n * 3 + 1, ++count) - } - } -} - -export default CollatzConjecture diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture.test.ts b/exercises/practice/collatz-conjecture/collatz-conjecture.test.ts index fc86533aa..c7082e059 100644 --- a/exercises/practice/collatz-conjecture/collatz-conjecture.test.ts +++ b/exercises/practice/collatz-conjecture/collatz-conjecture.test.ts @@ -1,37 +1,37 @@ -import CollatzConjecture from './collatz-conjecture' +import { steps } from './collatz-conjecture' describe('CollatzConjecture', () => { it('zero steps for one', () => { const expected = 0 - expect(CollatzConjecture.steps(1)).toBe(expected) + expect(steps(1)).toBe(expected) }) xit('divide if even', () => { const expected = 4 - expect(CollatzConjecture.steps(16)).toBe(expected) + expect(steps(16)).toBe(expected) }) xit('even and odd steps', () => { const expected = 9 - expect(CollatzConjecture.steps(12)).toBe(expected) + expect(steps(12)).toBe(expected) }) xit('Large number of even and odd steps', () => { const expected = 152 - expect(CollatzConjecture.steps(1000000)).toBe(expected) + expect(steps(1000000)).toBe(expected) }) xit('zero is an error', () => { const expected = 'Only positive numbers are allowed' expect(() => { - CollatzConjecture.steps(0) + steps(0) }).toThrowError(expected) }) xit('negative value is an error', () => { const expected = 'Only positive numbers are allowed' expect(() => { - CollatzConjecture.steps(-15) + steps(-15) }).toThrowError(expected) }) }) diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture.ts b/exercises/practice/collatz-conjecture/collatz-conjecture.ts index b06c09abb..e8a26b71d 100644 --- a/exercises/practice/collatz-conjecture/collatz-conjecture.ts +++ b/exercises/practice/collatz-conjecture/collatz-conjecture.ts @@ -1,7 +1,3 @@ -class CollatzConjecture { - static steps(/* Parameters go here */) { - // Your code here - } +export function steps(count: unknown): unknown { + throw new Error('Remove this statement and implement this function') } - -export default CollatzConjecture diff --git a/exercises/practice/complex-numbers/complex-numbers.example.ts b/exercises/practice/complex-numbers/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/complex-numbers/complex-numbers.example.ts rename to exercises/practice/complex-numbers/.meta/proof.ci.ts diff --git a/exercises/practice/complex-numbers/complex-numbers.test.ts b/exercises/practice/complex-numbers/complex-numbers.test.ts index 27c768231..bb100bf82 100644 --- a/exercises/practice/complex-numbers/complex-numbers.test.ts +++ b/exercises/practice/complex-numbers/complex-numbers.test.ts @@ -1,4 +1,4 @@ -import ComplexNumber from './complex-numbers' +import { ComplexNumber } from './complex-numbers' describe('Complex numbers', () => { it('Real part of a purely real number', () => { diff --git a/exercises/practice/complex-numbers/complex-numbers.ts b/exercises/practice/complex-numbers/complex-numbers.ts index e69de29bb..67631e9ca 100644 --- a/exercises/practice/complex-numbers/complex-numbers.ts +++ b/exercises/practice/complex-numbers/complex-numbers.ts @@ -0,0 +1,41 @@ +export class ComplexNumber { + constructor(real: unknown, imaginary: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public get real(): number { + throw new Error('Remove this statement and implement this function') + } + + public get imag(): number { + throw new Error('Remove this statement and implement this function') + } + + public add(other: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public sub(other: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public div(other: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public mul(other: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public get abs(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public get conj(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public get exp(): ComplexNumber { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/connect/connect.example.ts b/exercises/practice/connect/.meta/proof.ci.ts similarity index 98% rename from exercises/practice/connect/connect.example.ts rename to exercises/practice/connect/.meta/proof.ci.ts index 1859bf0ad..dab8b65be 100644 --- a/exercises/practice/connect/connect.example.ts +++ b/exercises/practice/connect/.meta/proof.ci.ts @@ -2,7 +2,7 @@ * "Player O" plays from top to bottom, "Player X" plays from left to right. * @param board */ -export default class ConnectBoard { +export class Board { private board: string[][] constructor(board: string[]) { diff --git a/exercises/practice/connect/connect.test.ts b/exercises/practice/connect/connect.test.ts index 617ba1ae8..fb7188d39 100644 --- a/exercises/practice/connect/connect.test.ts +++ b/exercises/practice/connect/connect.test.ts @@ -1,4 +1,4 @@ -import ConnectBoard from './connect' +import { Board } from './connect' describe('Judging a game of connect', () => { it('an empty board has no winner', () => { @@ -9,22 +9,22 @@ describe('Judging a game of connect', () => { ' . . . . .', ' . . . . .', ] - expect(new ConnectBoard(board).winner()).toEqual('') + expect(new Board(board).winner()).toEqual('') }) xit('X can win on a 1x1 board', () => { const board = ['X'] - expect(new ConnectBoard(board).winner()).toEqual('X') + expect(new Board(board).winner()).toEqual('X') }) xit('O can win on a 1x1 board', () => { const board = ['O'] - expect(new ConnectBoard(board).winner()).toEqual('O') + expect(new Board(board).winner()).toEqual('O') }) xit('only edges does not make a winner', () => { const board = ['O O O X', ' X . . X', ' X . . X', ' X O O O'] - expect(new ConnectBoard(board).winner()).toEqual('') + expect(new Board(board).winner()).toEqual('') }) xit('illegal diagonal does not make a winner', () => { @@ -35,7 +35,7 @@ describe('Judging a game of connect', () => { ' . O X .', ' X X O O', ] - expect(new ConnectBoard(board).winner()).toEqual('') + expect(new Board(board).winner()).toEqual('') }) xit('nobody wins crossing adjacent angles', () => { @@ -46,7 +46,7 @@ describe('Judging a game of connect', () => { ' . O . X', ' . . O .', ] - expect(new ConnectBoard(board).winner()).toEqual('') + expect(new Board(board).winner()).toEqual('') }) xit('X wins crossing from left to right', () => { @@ -57,7 +57,7 @@ describe('Judging a game of connect', () => { ' X X O X', ' . O X .', ] - expect(new ConnectBoard(board).winner()).toEqual('X') + expect(new Board(board).winner()).toEqual('X') }) xit('O wins crossing from top to bottom', () => { @@ -68,7 +68,7 @@ describe('Judging a game of connect', () => { ' X X O X', ' . O X .', ] - expect(new ConnectBoard(board).winner()).toEqual('O') + expect(new Board(board).winner()).toEqual('O') }) xit('X wins using a convoluted path', () => { @@ -79,7 +79,7 @@ describe('Judging a game of connect', () => { ' . X X . .', ' O O O O O', ] - expect(new ConnectBoard(board).winner()).toEqual('X') + expect(new Board(board).winner()).toEqual('X') }) xit('X wins using a spiral path', () => { @@ -94,6 +94,6 @@ describe('Judging a game of connect', () => { ' O O O O O O O X O', ' X X X X X X X X O', ] - expect(new ConnectBoard(board).winner()).toEqual('X') + expect(new Board(board).winner()).toEqual('X') }) }) diff --git a/exercises/practice/connect/connect.ts b/exercises/practice/connect/connect.ts index e69de29bb..f287ec94d 100644 --- a/exercises/practice/connect/connect.ts +++ b/exercises/practice/connect/connect.ts @@ -0,0 +1,9 @@ +export class Board { + constructor(board: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public winner(): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/crypto-square/.meta/proof.ci.ts b/exercises/practice/crypto-square/.meta/proof.ci.ts new file mode 100644 index 000000000..602f84ebd --- /dev/null +++ b/exercises/practice/crypto-square/.meta/proof.ci.ts @@ -0,0 +1,62 @@ +export class Crypto { + constructor(private readonly input: string) {} + + get plaintext() { + return this.input.toLowerCase().replace(/[^a-zA-Z0-9]/g, '') + } + + get ciphertext() { + const chunkSize = this.size + if (chunkSize === 0) { + return '' + } + + const splitRegex = new RegExp(`.{1,${chunkSize}}`, 'g') + return this.ciphertextSegments() + .join('') + .match(splitRegex) + .map((item) => item.padEnd(chunkSize, ' ')) + .join(' ') + } + + get size(): number { + const realLength = Math.sqrt(this.plaintext.length) + return Math.ceil(realLength) + } + + ciphertextSegments() { + const textSegments = this.plaintextSegments() + const columns = [] + let i + let j + let currentSegment + let currentLetter + + for (i = 0; i < this.size; i += 1) { + columns.push([]) + } + + for (i = 0; i < textSegments.length; i += 1) { + currentSegment = textSegments[i] + + for (j = 0; j < currentSegment.length; j += 1) { + currentLetter = currentSegment[j] + columns[j].push(currentLetter) + } + } + + for (i = 0; i < columns.length; i += 1) { + columns[i] = columns[i].join('') + } + + return columns + } + + plaintextSegments() { + const plainText = this.plaintext + const chunkSize = this.size + + const splitRegex = new RegExp(`.{1,${chunkSize}}`, 'g') + return plainText.match(splitRegex) + } +} diff --git a/exercises/practice/crypto-square/crypto-square.example.ts b/exercises/practice/crypto-square/crypto-square.example.ts deleted file mode 100644 index 2aa496471..000000000 --- a/exercises/practice/crypto-square/crypto-square.example.ts +++ /dev/null @@ -1,59 +0,0 @@ -export default class Square { - private input: string - - constructor(input: string) { - this.input = input - } - - public normalizePlaintext(): string { - return this.input.toLowerCase().replace(/[^a-zA-Z0-9]/g, '') - } - - public size(): number { - const realLength = Math.sqrt(this.normalizePlaintext().length) - return Math.ceil(realLength) - } - - public plaintextSegments(): ArrayLike | null { - const plainText = this.normalizePlaintext() - const chunkSize = this.size() - - const splitRegex = new RegExp(`.{1,${chunkSize}}`, 'g') - return plainText.match(splitRegex) - } - - public ciphertext(): string { - const textSegments = this.plaintextSegments() - let i - let j - const columns: string[][] = [] - let currentSegment - let currentLetter - - for (i = 0; i < this.size(); i++) { - columns.push([]) - } - - for (i = 0; i < textSegments!.length; i++) { - currentSegment = textSegments![i] - - for (j = 0; j < currentSegment.length; j++) { - currentLetter = currentSegment[j] - columns[j].push(currentLetter) - } - } - - const result: string[] = [] - for (i = 0; i < columns.length; i++) { - result[i] = columns[i].join('') - } - - return result.join('') - } - - public normalizeCiphertext(): string { - const chunkSize = this.size() - const splitRegex = new RegExp(`.{1,${chunkSize}}`, 'g') - return this.ciphertext().match(splitRegex)!.join(' ') - } -} diff --git a/exercises/practice/crypto-square/crypto-square.test.ts b/exercises/practice/crypto-square/crypto-square.test.ts index bf68a0efb..c2bdac385 100644 --- a/exercises/practice/crypto-square/crypto-square.test.ts +++ b/exercises/practice/crypto-square/crypto-square.test.ts @@ -1,59 +1,42 @@ -import Crypto from './crypto-square' +import { Crypto } from './crypto-square' describe('Crypto', () => { - it('normalize strange characters', () => { - const crypto = new Crypto('s#$%^&plunk') - expect(crypto.normalizePlaintext()).toEqual('splunk') + test('empty plaintext results in an empty ciphertext', () => { + const crypto = new Crypto('') + expect(crypto.ciphertext).toEqual('') }) - xit('normalize numbers', () => { - const crypto = new Crypto('1, 2, 3 GO!') - expect(crypto.normalizePlaintext()).toEqual('123go') + test('Lowercase', () => { + const crypto = new Crypto('A') + expect(crypto.ciphertext).toEqual('a') }) - xit('size of small square', () => { - const crypto = new Crypto('1234') - expect(crypto.size()).toEqual(2) + test('Remove spaces', () => { + const crypto = new Crypto(' b ') + expect(crypto.ciphertext).toEqual('b') }) - xit('size of small square with additional non-number chars', () => { - const crypto = new Crypto('1 2 3 4') - expect(crypto.size()).toEqual(2) + test('Remove punctuation', () => { + const crypto = new Crypto('@1,%!') + expect(crypto.ciphertext).toEqual('1') }) - xit('size of slightly larger square', () => { - const crypto = new Crypto('123456789') - expect(crypto.size()).toEqual(3) + xtest('9 character plaintext results in 3 chunks of 3 characters', () => { + const crypto = new Crypto('This is fun!') + expect(crypto.ciphertext).toEqual('tsf hiu isn') }) - xit('size of non-perfect square', () => { - const crypto = new Crypto('123456789abc') - expect(crypto.size()).toEqual(4) + xtest('8 character plaintext results in 3 chunks, the last one with a trailing space', () => { + const crypto = new Crypto('Chill out.') + expect(crypto.ciphertext).toEqual('clu hlt io ') }) - xit('plain text segments', () => { - const crypto = new Crypto('Never vex thine heart with idle woes') - expect(crypto.plaintextSegments()).toEqual([ - 'neverv', - 'exthin', - 'eheart', - 'withid', - 'lewoes', - ]) - }) - - xit('plain text segments', () => { - const crypto = new Crypto('ZOMG! ZOMBIES!!!') - expect(crypto.plaintextSegments()).toEqual(['zomg', 'zomb', 'ies']) - }) - - xit('cipher text', () => { - const crypto = new Crypto('Time is an illusion. Lunchtime doubly so.') - expect(crypto.ciphertext()).toEqual('tasneyinicdsmiohooelntuillibsuuml') - }) - - xit('cipher text', () => { - const crypto = new Crypto('We all know interspecies romance is weird.') - expect(crypto.ciphertext()).toEqual('wneiaweoreneawssciliprerlneoidktcms') + test.skip('54 character plaintext results in 7 chunks, the last two with trailing spaces', () => { + const crypto = new Crypto( + 'If man was meant to stay on the ground, god would have given us roots.' + ) + expect(crypto.ciphertext).toEqual( + 'imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ' + ) }) }) diff --git a/exercises/practice/crypto-square/crypto-square.ts b/exercises/practice/crypto-square/crypto-square.ts index e69de29bb..f5c9aea66 100644 --- a/exercises/practice/crypto-square/crypto-square.ts +++ b/exercises/practice/crypto-square/crypto-square.ts @@ -0,0 +1,9 @@ +export class Crypto { + constructor(plainText: unknown) { + throw new Error('Remove this statement and implement this function') + } + + get ciphertext(): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/custom-set/custom-set.example.ts b/exercises/practice/custom-set/.meta/proof.ci.ts similarity index 97% rename from exercises/practice/custom-set/custom-set.example.ts rename to exercises/practice/custom-set/.meta/proof.ci.ts index 60ffc44dd..b3ea1cd3a 100644 --- a/exercises/practice/custom-set/custom-set.example.ts +++ b/exercises/practice/custom-set/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default class CustomSet { +export class CustomSet { private data: Set constructor(data: T[] = []) { diff --git a/exercises/practice/custom-set/custom-set.test.ts b/exercises/practice/custom-set/custom-set.test.ts index a4350238c..a9d56a2ab 100644 --- a/exercises/practice/custom-set/custom-set.test.ts +++ b/exercises/practice/custom-set/custom-set.test.ts @@ -1,4 +1,4 @@ -import CustomSet from './custom-set' +import { CustomSet } from './custom-set' describe('CustomSet', () => { describe('empty: returns true if the set contains no elements', () => { diff --git a/exercises/practice/custom-set/custom-set.ts b/exercises/practice/custom-set/custom-set.ts index e69de29bb..c7117a36e 100644 --- a/exercises/practice/custom-set/custom-set.ts +++ b/exercises/practice/custom-set/custom-set.ts @@ -0,0 +1,41 @@ +export class CustomSet { + constructor(initial?: unknown) { + throw new Error('Remove this statement and implement this function') + } + + empty(): unknown { + throw new Error('Remove this statement and implement this function') + } + + contains(element: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + add(element: unknown): CustomSet { + throw new Error('Remove this statement and implement this function') + } + + subset(other: unknown): CustomSet { + throw new Error('Remove this statement and implement this function') + } + + disjoint(other: unknown): CustomSet { + throw new Error('Remove this statement and implement this function') + } + + eql(other: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + union(other: unknown): CustomSet { + throw new Error('Remove this statement and implement this function') + } + + intersection(other: unknown): CustomSet { + throw new Error('Remove this statement and implement this function') + } + + difference(other: unknown): CustomSet { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/diamond/.meta/proof.ci.ts b/exercises/practice/diamond/.meta/proof.ci.ts new file mode 100644 index 000000000..2f273acee --- /dev/null +++ b/exercises/practice/diamond/.meta/proof.ci.ts @@ -0,0 +1,48 @@ +export function makeDiamond(input: string): string { + // A = 0, Z = 25 + const inputIx = input.charCodeAt(0) - 65 + const lines = [] + for (let i = 0; i <= inputIx; i++) { + lines.push(getLine(inputIx, i)) + } + for (let i = inputIx - 1; i >= 0; i--) { + lines.push(lines[i]) + } + return lines.join('\n') + '\n' +} + +function getAlphaString(index: number): string { + if (index === 0) { + return `A` + } + const char = String.fromCharCode(index + 65) + const padding = ' '.repeat((index - 1) * 2 + 1) + return `${char}${padding}${char}` +} + +function getLine(inputIx: number, index: number): string { + const difference = inputIx - index + const alphaStr = getAlphaString(index) + return `${padString(alphaStr, difference)}` +} + +function padString(str: string, times: number): string { + const spaces = ' '.repeat(times) + return `${spaces}${str}${spaces}` +} + +/* +ex: D -> +loop: + A: D=3, A=0 -> 3 spaces, A, 3 spaces (special case) + B: D=3, B=1 -> 2 spaces, B, 1 space, B, 2 spaces + C: D=3, C=2 -> 1 space, C, 3 spaces, C, 1 space + D: D=3, D=3 -> 0 spaces, D, 5 spaces, D, 0 spaces + A + B B + C C +D D + C C + B B + A +*/ diff --git a/exercises/practice/diamond/diamond.example.ts b/exercises/practice/diamond/diamond.example.ts deleted file mode 100644 index 09c79b1a2..000000000 --- a/exercises/practice/diamond/diamond.example.ts +++ /dev/null @@ -1,51 +0,0 @@ -class Diamond { - public makeDiamond(input: string): string { - // A = 0, Z = 25 - const inputIx = input.charCodeAt(0) - 65 - const lines = [] - for (let i = 0; i <= inputIx; i++) { - lines.push(this.getLine(inputIx, i)) - } - for (let i = inputIx - 1; i >= 0; i--) { - lines.push(lines[i]) - } - return lines.join('\n') + '\n' - } - - private getAlphaString(index: number): string { - if (index === 0) { - return `A` - } - const char = String.fromCharCode(index + 65) - const padding = ' '.repeat((index - 1) * 2 + 1) - return `${char}${padding}${char}` - } - - private getLine(inputIx: number, index: number): string { - const difference = inputIx - index - const alphaStr = this.getAlphaString(index) - return `${this.padString(alphaStr, difference)}` - } - - private padString(str: string, times: number): string { - const spaces = ' '.repeat(times) - return `${spaces}${str}${spaces}` - } -} - -/* -ex: D -> -loop: - A: D=3, A=0 -> 3 spaces, A, 3 spaces (special case) - B: D=3, B=1 -> 2 spaces, B, 1 space, B, 2 spaces - C: D=3, C=2 -> 1 space, C, 3 spaces, C, 1 space - D: D=3, D=3 -> 0 spaces, D, 5 spaces, D, 0 spaces - A - B B - C C -D D - C C - B B - A -*/ -export default Diamond diff --git a/exercises/practice/diamond/diamond.test.ts b/exercises/practice/diamond/diamond.test.ts index a97a09875..173795911 100644 --- a/exercises/practice/diamond/diamond.test.ts +++ b/exercises/practice/diamond/diamond.test.ts @@ -1,27 +1,26 @@ -import Diamond from './diamond' +import { makeDiamond } from './diamond' function diamondify(parts: TemplateStringsArray): string { + // prettier-ignore return ( parts[0] - .trim() // Remove leading and trailing whitespace - .split('\n') // Consider each row + .trim() // Remove leading and trailing whitespace + .split('\n') // Consider each row .map((line) => { return line - .trim() // Remove whitespace at start (and end) - .replace(/·/g, ' ') // Use spaces instead of · + .trim() // Remove whitespace at start (and end) + .replace(/·/g, ' ') // Use spaces instead of · }) - .filter(Boolean) // Remove empty rows (if any) - .join('\n') + // Turn back into a single string - '\n' - ) // Should have a final newline + .filter(Boolean) // Remove empty rows (if any) + .join('\n') + // Turn back into a single string + '\n' // Should have a final newline + ) } describe('Make diamond function', () => { - const diamond = new Diamond() - test('test letter A', () => { const result = 'A\n' - expect(diamond.makeDiamond('A')).toEqual(result) + expect(makeDiamond('A')).toEqual(result) }) test('test letter C', () => { @@ -32,7 +31,7 @@ describe('Make diamond function', () => { ·B·B· ··A·· ` - expect(diamond.makeDiamond('C')).toEqual(result) + expect(makeDiamond('C')).toEqual(result) }) test('test letter E', () => { @@ -47,6 +46,6 @@ describe('Make diamond function', () => { ···B·B··· ····A···· ` - expect(diamond.makeDiamond('E')).toEqual(result) + expect(makeDiamond('E')).toEqual(result) }) }) diff --git a/exercises/practice/diamond/diamond.ts b/exercises/practice/diamond/diamond.ts index e69de29bb..04f499562 100644 --- a/exercises/practice/diamond/diamond.ts +++ b/exercises/practice/diamond/diamond.ts @@ -0,0 +1,3 @@ +export function makeDiamond(character: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/difference-of-squares/difference-of-squares.example.ts b/exercises/practice/difference-of-squares/.meta/proof.ci.ts similarity index 95% rename from exercises/practice/difference-of-squares/difference-of-squares.example.ts rename to exercises/practice/difference-of-squares/.meta/proof.ci.ts index 640daf805..0414be87f 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares.example.ts +++ b/exercises/practice/difference-of-squares/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default class Squares { +export class Squares { public readonly squareOfSum: number public readonly sumOfSquares: number public readonly difference: number diff --git a/exercises/practice/difference-of-squares/difference-of-squares.test.ts b/exercises/practice/difference-of-squares/difference-of-squares.test.ts index 22ed877cc..dd6b6a7d4 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares.test.ts +++ b/exercises/practice/difference-of-squares/difference-of-squares.test.ts @@ -1,4 +1,4 @@ -import Squares from './difference-of-squares' +import { Squares } from './difference-of-squares' describe('Squares', () => { describe('up to 5', () => { diff --git a/exercises/practice/difference-of-squares/difference-of-squares.ts b/exercises/practice/difference-of-squares/difference-of-squares.ts index e69de29bb..9fd16860b 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares.ts +++ b/exercises/practice/difference-of-squares/difference-of-squares.ts @@ -0,0 +1,17 @@ +export class Squares { + constructor(count: unknown) { + throw new Error('Remove this statement and implement this function') + } + + get sumOfSquares(): unknown { + throw new Error('Remove this statement and implement this function') + } + + get squareOfSum(): unknown { + throw new Error('Remove this statement and implement this function') + } + + get difference(): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/diffie-hellman/.meta/proof.ci.ts b/exercises/practice/diffie-hellman/.meta/proof.ci.ts new file mode 100644 index 000000000..81800c974 --- /dev/null +++ b/exercises/practice/diffie-hellman/.meta/proof.ci.ts @@ -0,0 +1,113 @@ +// array of first 1000 primes. +// prettier-ignore +const PRIMES = [ + 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, 1009, 1013, 1019, + 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, + 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, + 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, + 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, + 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, + 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, + 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, + 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, + 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, + 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, + 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, + 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, + 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, + 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, + 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, + 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, + 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, + 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, + 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, + 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, + 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, + 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, + 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, + 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, + 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, + 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, + 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, + 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, + 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, + 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, + 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, + 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, + 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, + 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, + 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, + 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, + 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, + 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, + 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, + 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, + 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, + 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, + 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, + 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, + 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, + 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, + 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, + 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, + 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, + 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, + 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, + 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, + 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, + 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, + 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, + 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, + 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, + 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, + 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, + 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, + 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, +]; + +export class DiffieHellman { + constructor(private readonly p: number, private readonly g: number) { + if (!DiffieHellman.validateInitialArguments(p, g)) { + throw Error('Constructor arguments are out of range or non-prime!') + } + } + + public getPublicKey(privateKey: number): number { + if (privateKey <= 1 || privateKey > this.p - 1) { + throw Error( + 'Private key a must be greater than one but less than modulus parameter p!' + ) + } + return this.g ** privateKey % this.p + } + + public getSecret(theirPublicKey: number, ourPrivateKey: number): number { + return theirPublicKey ** ourPrivateKey % this.p + } + + private static validateInitialArguments(p: number, g: number): boolean { + const BIGGEST_PRIME = PRIMES[PRIMES.length - 1] + + return ( + p >= 2 && + g >= 2 && + p <= BIGGEST_PRIME && + g <= BIGGEST_PRIME && + PRIMES.includes(p) && + PRIMES.includes(g) + ) + } +} diff --git a/exercises/practice/diffie-hellman/diffie-hellman.example.ts b/exercises/practice/diffie-hellman/diffie-hellman.example.ts deleted file mode 100644 index 0a09d6823..000000000 --- a/exercises/practice/diffie-hellman/diffie-hellman.example.ts +++ /dev/null @@ -1,1045 +0,0 @@ -// array of first 1000 primes. -const PRIMES = [ - 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, - 1009, - 1013, - 1019, - 1021, - 1031, - 1033, - 1039, - 1049, - 1051, - 1061, - 1063, - 1069, - 1087, - 1091, - 1093, - 1097, - 1103, - 1109, - 1117, - 1123, - 1129, - 1151, - 1153, - 1163, - 1171, - 1181, - 1187, - 1193, - 1201, - 1213, - 1217, - 1223, - 1229, - 1231, - 1237, - 1249, - 1259, - 1277, - 1279, - 1283, - 1289, - 1291, - 1297, - 1301, - 1303, - 1307, - 1319, - 1321, - 1327, - 1361, - 1367, - 1373, - 1381, - 1399, - 1409, - 1423, - 1427, - 1429, - 1433, - 1439, - 1447, - 1451, - 1453, - 1459, - 1471, - 1481, - 1483, - 1487, - 1489, - 1493, - 1499, - 1511, - 1523, - 1531, - 1543, - 1549, - 1553, - 1559, - 1567, - 1571, - 1579, - 1583, - 1597, - 1601, - 1607, - 1609, - 1613, - 1619, - 1621, - 1627, - 1637, - 1657, - 1663, - 1667, - 1669, - 1693, - 1697, - 1699, - 1709, - 1721, - 1723, - 1733, - 1741, - 1747, - 1753, - 1759, - 1777, - 1783, - 1787, - 1789, - 1801, - 1811, - 1823, - 1831, - 1847, - 1861, - 1867, - 1871, - 1873, - 1877, - 1879, - 1889, - 1901, - 1907, - 1913, - 1931, - 1933, - 1949, - 1951, - 1973, - 1979, - 1987, - 1993, - 1997, - 1999, - 2003, - 2011, - 2017, - 2027, - 2029, - 2039, - 2053, - 2063, - 2069, - 2081, - 2083, - 2087, - 2089, - 2099, - 2111, - 2113, - 2129, - 2131, - 2137, - 2141, - 2143, - 2153, - 2161, - 2179, - 2203, - 2207, - 2213, - 2221, - 2237, - 2239, - 2243, - 2251, - 2267, - 2269, - 2273, - 2281, - 2287, - 2293, - 2297, - 2309, - 2311, - 2333, - 2339, - 2341, - 2347, - 2351, - 2357, - 2371, - 2377, - 2381, - 2383, - 2389, - 2393, - 2399, - 2411, - 2417, - 2423, - 2437, - 2441, - 2447, - 2459, - 2467, - 2473, - 2477, - 2503, - 2521, - 2531, - 2539, - 2543, - 2549, - 2551, - 2557, - 2579, - 2591, - 2593, - 2609, - 2617, - 2621, - 2633, - 2647, - 2657, - 2659, - 2663, - 2671, - 2677, - 2683, - 2687, - 2689, - 2693, - 2699, - 2707, - 2711, - 2713, - 2719, - 2729, - 2731, - 2741, - 2749, - 2753, - 2767, - 2777, - 2789, - 2791, - 2797, - 2801, - 2803, - 2819, - 2833, - 2837, - 2843, - 2851, - 2857, - 2861, - 2879, - 2887, - 2897, - 2903, - 2909, - 2917, - 2927, - 2939, - 2953, - 2957, - 2963, - 2969, - 2971, - 2999, - 3001, - 3011, - 3019, - 3023, - 3037, - 3041, - 3049, - 3061, - 3067, - 3079, - 3083, - 3089, - 3109, - 3119, - 3121, - 3137, - 3163, - 3167, - 3169, - 3181, - 3187, - 3191, - 3203, - 3209, - 3217, - 3221, - 3229, - 3251, - 3253, - 3257, - 3259, - 3271, - 3299, - 3301, - 3307, - 3313, - 3319, - 3323, - 3329, - 3331, - 3343, - 3347, - 3359, - 3361, - 3371, - 3373, - 3389, - 3391, - 3407, - 3413, - 3433, - 3449, - 3457, - 3461, - 3463, - 3467, - 3469, - 3491, - 3499, - 3511, - 3517, - 3527, - 3529, - 3533, - 3539, - 3541, - 3547, - 3557, - 3559, - 3571, - 3581, - 3583, - 3593, - 3607, - 3613, - 3617, - 3623, - 3631, - 3637, - 3643, - 3659, - 3671, - 3673, - 3677, - 3691, - 3697, - 3701, - 3709, - 3719, - 3727, - 3733, - 3739, - 3761, - 3767, - 3769, - 3779, - 3793, - 3797, - 3803, - 3821, - 3823, - 3833, - 3847, - 3851, - 3853, - 3863, - 3877, - 3881, - 3889, - 3907, - 3911, - 3917, - 3919, - 3923, - 3929, - 3931, - 3943, - 3947, - 3967, - 3989, - 4001, - 4003, - 4007, - 4013, - 4019, - 4021, - 4027, - 4049, - 4051, - 4057, - 4073, - 4079, - 4091, - 4093, - 4099, - 4111, - 4127, - 4129, - 4133, - 4139, - 4153, - 4157, - 4159, - 4177, - 4201, - 4211, - 4217, - 4219, - 4229, - 4231, - 4241, - 4243, - 4253, - 4259, - 4261, - 4271, - 4273, - 4283, - 4289, - 4297, - 4327, - 4337, - 4339, - 4349, - 4357, - 4363, - 4373, - 4391, - 4397, - 4409, - 4421, - 4423, - 4441, - 4447, - 4451, - 4457, - 4463, - 4481, - 4483, - 4493, - 4507, - 4513, - 4517, - 4519, - 4523, - 4547, - 4549, - 4561, - 4567, - 4583, - 4591, - 4597, - 4603, - 4621, - 4637, - 4639, - 4643, - 4649, - 4651, - 4657, - 4663, - 4673, - 4679, - 4691, - 4703, - 4721, - 4723, - 4729, - 4733, - 4751, - 4759, - 4783, - 4787, - 4789, - 4793, - 4799, - 4801, - 4813, - 4817, - 4831, - 4861, - 4871, - 4877, - 4889, - 4903, - 4909, - 4919, - 4931, - 4933, - 4937, - 4943, - 4951, - 4957, - 4967, - 4969, - 4973, - 4987, - 4993, - 4999, - 5003, - 5009, - 5011, - 5021, - 5023, - 5039, - 5051, - 5059, - 5077, - 5081, - 5087, - 5099, - 5101, - 5107, - 5113, - 5119, - 5147, - 5153, - 5167, - 5171, - 5179, - 5189, - 5197, - 5209, - 5227, - 5231, - 5233, - 5237, - 5261, - 5273, - 5279, - 5281, - 5297, - 5303, - 5309, - 5323, - 5333, - 5347, - 5351, - 5381, - 5387, - 5393, - 5399, - 5407, - 5413, - 5417, - 5419, - 5431, - 5437, - 5441, - 5443, - 5449, - 5471, - 5477, - 5479, - 5483, - 5501, - 5503, - 5507, - 5519, - 5521, - 5527, - 5531, - 5557, - 5563, - 5569, - 5573, - 5581, - 5591, - 5623, - 5639, - 5641, - 5647, - 5651, - 5653, - 5657, - 5659, - 5669, - 5683, - 5689, - 5693, - 5701, - 5711, - 5717, - 5737, - 5741, - 5743, - 5749, - 5779, - 5783, - 5791, - 5801, - 5807, - 5813, - 5821, - 5827, - 5839, - 5843, - 5849, - 5851, - 5857, - 5861, - 5867, - 5869, - 5879, - 5881, - 5897, - 5903, - 5923, - 5927, - 5939, - 5953, - 5981, - 5987, - 6007, - 6011, - 6029, - 6037, - 6043, - 6047, - 6053, - 6067, - 6073, - 6079, - 6089, - 6091, - 6101, - 6113, - 6121, - 6131, - 6133, - 6143, - 6151, - 6163, - 6173, - 6197, - 6199, - 6203, - 6211, - 6217, - 6221, - 6229, - 6247, - 6257, - 6263, - 6269, - 6271, - 6277, - 6287, - 6299, - 6301, - 6311, - 6317, - 6323, - 6329, - 6337, - 6343, - 6353, - 6359, - 6361, - 6367, - 6373, - 6379, - 6389, - 6397, - 6421, - 6427, - 6449, - 6451, - 6469, - 6473, - 6481, - 6491, - 6521, - 6529, - 6547, - 6551, - 6553, - 6563, - 6569, - 6571, - 6577, - 6581, - 6599, - 6607, - 6619, - 6637, - 6653, - 6659, - 6661, - 6673, - 6679, - 6689, - 6691, - 6701, - 6703, - 6709, - 6719, - 6733, - 6737, - 6761, - 6763, - 6779, - 6781, - 6791, - 6793, - 6803, - 6823, - 6827, - 6829, - 6833, - 6841, - 6857, - 6863, - 6869, - 6871, - 6883, - 6899, - 6907, - 6911, - 6917, - 6947, - 6949, - 6959, - 6961, - 6967, - 6971, - 6977, - 6983, - 6991, - 6997, - 7001, - 7013, - 7019, - 7027, - 7039, - 7043, - 7057, - 7069, - 7079, - 7103, - 7109, - 7121, - 7127, - 7129, - 7151, - 7159, - 7177, - 7187, - 7193, - 7207, - 7211, - 7213, - 7219, - 7229, - 7237, - 7243, - 7247, - 7253, - 7283, - 7297, - 7307, - 7309, - 7321, - 7331, - 7333, - 7349, - 7351, - 7369, - 7393, - 7411, - 7417, - 7433, - 7451, - 7457, - 7459, - 7477, - 7481, - 7487, - 7489, - 7499, - 7507, - 7517, - 7523, - 7529, - 7537, - 7541, - 7547, - 7549, - 7559, - 7561, - 7573, - 7577, - 7583, - 7589, - 7591, - 7603, - 7607, - 7621, - 7639, - 7643, - 7649, - 7669, - 7673, - 7681, - 7687, - 7691, - 7699, - 7703, - 7717, - 7723, - 7727, - 7741, - 7753, - 7757, - 7759, - 7789, - 7793, - 7817, - 7823, - 7829, - 7841, - 7853, - 7867, - 7873, - 7877, - 7879, - 7883, - 7901, - 7907, - 7919, -] - -export default class DiffieHellman { - private readonly p: number - private readonly g: number - - constructor(p: number, g: number) { - if (!this.validateInitialArguments(p, g)) { - throw Error('Constructor arguments are out of range or non-prime!') - } - - this.p = p - this.g = g - } - - public getPublicKeyFromPrivateKey(privateKey: number): number { - if (privateKey <= 1 || privateKey > this.p - 1) { - throw Error( - 'Private key a must be greater than one but less than modulus parameter p!' - ) - } - return this.g ** privateKey % this.p - } - - public getSharedSecret( - ourPrivateKey: number, - theirPublicKey: number - ): number { - return theirPublicKey ** ourPrivateKey % this.p - } - - private validateInitialArguments(p: number, g: number): boolean { - const BIGGEST_PRIME = PRIMES[PRIMES.length - 1] - return ( - p >= 2 && - g >= 2 && - p <= BIGGEST_PRIME && - g <= BIGGEST_PRIME && - PRIMES.includes(p) && - PRIMES.includes(g) - ) - } -} diff --git a/exercises/practice/diffie-hellman/diffie-hellman.test.ts b/exercises/practice/diffie-hellman/diffie-hellman.test.ts index 5da4af36b..b1ee3e7cb 100644 --- a/exercises/practice/diffie-hellman/diffie-hellman.test.ts +++ b/exercises/practice/diffie-hellman/diffie-hellman.test.ts @@ -1,79 +1,89 @@ -import DiffieHellman from './diffie-hellman' +import { DiffieHellman } from './diffie-hellman' describe('diffie-hellman', () => { - const p = 23 - const g = 5 - const diffieHellman = new DiffieHellman(p, g) - - const alicePrivateKey = 6 - const alicePublicKey = 8 - - const bobPrivateKey = 15 - const bobPublicKey = 19 - - it('throws an error if the constructor arguments are out of range', () => { + test('throws an error if the constructor arguments are out of range', () => { expect(() => { new DiffieHellman(0, 9999) }).toThrow() }) - it('throws an error if the constructor arguments are not prime', () => { + xtest('throws an error if the constructor arguments are not prime', () => { expect(() => { new DiffieHellman(10, 13) }).toThrow() }) - it('throws an error if private key is negative', () => { - expect(() => { - diffieHellman.getPublicKeyFromPrivateKey(-1) - }).toThrow() - }) + describe('input validation', () => { + const p = 23 + const g = 5 + const diffieHellman = new DiffieHellman(p, g) - it('throws an error if private key is zero', () => { - expect(() => { - diffieHellman.getPublicKeyFromPrivateKey(0) - }).toThrow() - }) + xtest('throws an error if private key is negative', () => { + expect(() => { + diffieHellman.getPublicKey(-1) + }).toThrow() + }) - it('throws an error if private key is one', () => { - expect(() => { - diffieHellman.getPublicKeyFromPrivateKey(1) - }).toThrow() - }) + xtest('throws an error if private key is zero', () => { + expect(() => { + diffieHellman.getPublicKey(0) + }).toThrow() + }) - it('throws an error if private key equals the modulus parameter p', () => { - expect(() => { - diffieHellman.getPublicKeyFromPrivateKey(p) - }).toThrow() - }) + xtest('throws an error if private key is one', () => { + expect(() => { + diffieHellman.getPublicKey(1) + }).toThrow() + }) - it('throws an error if private key is greater than the modulus parameter p', () => { - expect(() => { - diffieHellman.getPublicKeyFromPrivateKey(p + 1) - }).toThrow() + xtest('throws an error if private key equals the modulus parameter p', () => { + expect(() => { + diffieHellman.getPublicKey(p) + }).toThrow() + }) + + xtest('throws an error if private key is greater than the modulus parameter p', () => { + expect(() => { + diffieHellman.getPublicKey(p + 1) + }).toThrow() + }) }) - it('when given a private key, returns the correct public one', () => { - expect(diffieHellman.getPublicKeyFromPrivateKey(alicePrivateKey)).toEqual( - alicePublicKey - ) + describe('stateless calculation', () => { + const diffieHellman = new DiffieHellman(23, 5) + + const alicePrivateKey = 6 + const alicePublicKey = 8 + + const bobPrivateKey = 15 + const bobPublicKey = 19 + + xtest('can calculate public key using private key', () => { + expect(diffieHellman.getPublicKey(alicePrivateKey)).toEqual( + alicePublicKey + ) + }) + + xtest('can calculate public key when given a different private key', () => { + expect(diffieHellman.getPublicKey(bobPrivateKey)).toEqual(bobPublicKey) + }) }) - it('when given a different private key, returns the correct public one', () => { - expect(diffieHellman.getPublicKeyFromPrivateKey(bobPrivateKey)).toEqual( - bobPublicKey - ) + xtest("can calculate secret using other party's public key", () => { + expect(new DiffieHellman(23, 5).getSecret(19, 6)).toEqual(2) }) - it('can generate a shared secret from our private key and their public key', () => { - const sharedSecret = 2 + xtest('key exchange', () => { + const diffieHellman = new DiffieHellman(23, 5) + + const alicePrivateKey = 6 + const bobPrivateKey = 15 + const alicePublicKey = diffieHellman.getPublicKey(alicePrivateKey) + const bobPublicKey = diffieHellman.getPublicKey(bobPrivateKey) - expect( - diffieHellman.getSharedSecret(alicePrivateKey, bobPublicKey) - ).toEqual(sharedSecret) + const secretA = diffieHellman.getSecret(bobPublicKey, alicePrivateKey) + const secretB = diffieHellman.getSecret(alicePublicKey, bobPrivateKey) - expect( - diffieHellman.getSharedSecret(bobPrivateKey, alicePublicKey) - ).toEqual(sharedSecret) + expect(secretA).toEqual(secretB) }) }) diff --git a/exercises/practice/diffie-hellman/diffie-hellman.ts b/exercises/practice/diffie-hellman/diffie-hellman.ts index e69de29bb..3c18ba701 100644 --- a/exercises/practice/diffie-hellman/diffie-hellman.ts +++ b/exercises/practice/diffie-hellman/diffie-hellman.ts @@ -0,0 +1,13 @@ +export class DiffieHellman { + constructor(p: unknown, g: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public getPublicKey(privateKey: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } + + public getSecret(theirPublicKey: unknown, myPrivateKey: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/etl/etl.example.ts b/exercises/practice/etl/.meta/proof.ci.ts similarity index 86% rename from exercises/practice/etl/etl.example.ts rename to exercises/practice/etl/.meta/proof.ci.ts index 1a386c6b9..0d5204372 100644 --- a/exercises/practice/etl/etl.example.ts +++ b/exercises/practice/etl/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -function transform(input: { +export function transform(input: { [key: string]: string[] }): { [key: string]: number } { const phase1: { [key: string]: number } = {} @@ -13,5 +13,3 @@ function transform(input: { } return phase1 } - -export default transform diff --git a/exercises/practice/etl/etl.test.ts b/exercises/practice/etl/etl.test.ts index 102be3105..be26c222c 100644 --- a/exercises/practice/etl/etl.test.ts +++ b/exercises/practice/etl/etl.test.ts @@ -1,4 +1,4 @@ -import transform from './etl' +import { transform } from './etl' describe('Transform', () => { it('transforms one value', () => { diff --git a/exercises/practice/etl/etl.ts b/exercises/practice/etl/etl.ts index de3bd6dd9..c7834572d 100644 --- a/exercises/practice/etl/etl.ts +++ b/exercises/practice/etl/etl.ts @@ -1,5 +1,3 @@ -function transform(/* Parameters go here */): { - // Your code here +export function transform(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') } - -export default transform diff --git a/exercises/practice/flatten-array/.meta/proof.ci.ts b/exercises/practice/flatten-array/.meta/proof.ci.ts new file mode 100644 index 000000000..fc272245b --- /dev/null +++ b/exercises/practice/flatten-array/.meta/proof.ci.ts @@ -0,0 +1,9 @@ +export function flatten>(arr: T): A[] { + return arr + .reduce( + (acc: A[], el) => + Array.isArray(el) ? acc.concat(this.flatten(el)) : acc.concat(el), + [] + ) + .filter((el: A) => el !== null && el !== undefined) +} diff --git a/exercises/practice/flatten-array/flatten-array.example.ts b/exercises/practice/flatten-array/flatten-array.example.ts deleted file mode 100644 index e234e52ba..000000000 --- a/exercises/practice/flatten-array/flatten-array.example.ts +++ /dev/null @@ -1,13 +0,0 @@ -class FlattenArray { - public static flatten>(arr: T): A[] { - return arr - .reduce( - (acc: A[], el) => - Array.isArray(el) ? acc.concat(this.flatten(el)) : acc.concat(el), - [] - ) - .filter((el: A) => el !== null && el !== undefined) - } -} - -export default FlattenArray diff --git a/exercises/practice/flatten-array/flatten-array.test.ts b/exercises/practice/flatten-array/flatten-array.test.ts index 88269b6f2..ac0d7a6a5 100644 --- a/exercises/practice/flatten-array/flatten-array.test.ts +++ b/exercises/practice/flatten-array/flatten-array.test.ts @@ -1,46 +1,37 @@ -import FlattenArray from './flatten-array' +import { flatten } from './flatten-array' describe('Flatten Array', () => { it('no nesting', () => { const expected = [0, 1, 2] - expect(FlattenArray.flatten([0, 1, 2])).toEqual(expected) + expect(flatten([0, 1, 2])).toEqual(expected) }) xit('flattens array with just integers present', () => { const expected = [1, 2, 3, 4, 5, 6, 7, 8] - expect(FlattenArray.flatten([1, [2, 3, 4, 5, 6, 7], 8])).toEqual(expected) + expect(flatten([1, [2, 3, 4, 5, 6, 7], 8])).toEqual(expected) }) xit('5 level nesting', () => { const expected = [0, 2, 2, 3, 8, 100, 4, 50, -2] - expect( - FlattenArray.flatten([0, 2, [[2, 3], 8, 100, 4, [[[50]]]], -2]) - ).toEqual(expected) + expect(flatten([0, 2, [[2, 3], 8, 100, 4, [[[50]]]], -2])).toEqual(expected) }) xit('6 level nesting', () => { const expected = [1, 2, 3, 4, 5, 6, 7, 8] - expect(FlattenArray.flatten([1, [2, [[3]], [4, [[5]]], 6, 7], 8])).toEqual( - expected - ) + expect(flatten([1, [2, [[3]], [4, [[5]]], 6, 7], 8])).toEqual(expected) }) xit('6 level nest list with null values', () => { const expected = [0, 2, 2, 3, 8, 100, -2] expect( - FlattenArray.flatten([ - 0, - 2, - [[2, 3], 8, [[100]], undefined, [[undefined]]], - -2, - ]) + flatten([0, 2, [[2, 3], 8, [[100]], undefined, [[undefined]]], -2]) ).toEqual(expected) }) xit('all values in nested list are null', () => { const expected: number[] = [] expect( - FlattenArray.flatten([ + flatten([ undefined, [[[undefined]]], undefined, diff --git a/exercises/practice/flatten-array/flatten-array.ts b/exercises/practice/flatten-array/flatten-array.ts index e69de29bb..60a0b7e71 100644 --- a/exercises/practice/flatten-array/flatten-array.ts +++ b/exercises/practice/flatten-array/flatten-array.ts @@ -0,0 +1,3 @@ +export function flatten(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/food-chain/.meta/proof.ci.ts b/exercises/practice/food-chain/.meta/proof.ci.ts new file mode 100644 index 000000000..904919887 --- /dev/null +++ b/exercises/practice/food-chain/.meta/proof.ci.ts @@ -0,0 +1,72 @@ +const animals = [ + 'fly', + 'spider', + 'bird', + 'cat', + 'dog', + 'goat', + 'cow', + 'horse', +] as const + +export function verses(start = 1, end = 8): string { + if (end < start) { + throw new Error('end should be smaller than the start') + } + let verses = '' + + for (; start <= end; start += 1) { + verses += verse(start) + if (start !== end) { + verses += '\n' + } + } + return verses +} + +export function verse(num: number): string { + let result = '' + + let index = num - 1 + result += `I know an old lady who swallowed a ${animals[index]}.\n` + + switch (num) { + case 2: + result += 'It wriggled and jiggled and tickled inside her.\n' + break + case 3: + result += 'How absurd to swallow a bird!\n' + break + case 4: + result += 'Imagine that, to swallow a cat!\n' + break + case 5: + result += 'What a hog, to swallow a dog!\n' + break + case 6: + result += 'Just opened her throat and swallowed a goat!\n' + break + case 7: + result += "I don't know how she swallowed a cow!\n" + break + case 8: + result += "She's dead, of course!\n" + return result + default: + break + } + + while (index >= 1) { + result += `She swallowed the ${animals[index]} to catch the ${ + animals[index - 1] + }` + if (index === 2) { + result += ' that wriggled and jiggled and tickled inside her' + } + result += '.\n' + index -= 1 + } + result += "I don't know why she swallowed the fly. Perhaps she'll die." + + return result + '\n' +} diff --git a/exercises/practice/food-chain/food-chain.example.ts b/exercises/practice/food-chain/food-chain.example.ts deleted file mode 100644 index c36342bf3..000000000 --- a/exercises/practice/food-chain/food-chain.example.ts +++ /dev/null @@ -1,74 +0,0 @@ -const animals = [ - 'fly', - 'spider', - 'bird', - 'cat', - 'dog', - 'goat', - 'cow', - 'horse', -] as const - -export default class FoodChain { - public static verses(start = 1, end = 8): string { - if (end < start) { - throw new Error('end should be smaller than the start') - } - let verses = '' - - for (; start <= end; start += 1) { - verses += FoodChain.verse(start) - if (start !== end) { - verses += '\n' - } - } - return verses - } - - public static verse(num: number): string { - let result = '' - - let index = num - 1 - result += `I know an old lady who swallowed a ${animals[index]}.\n` - - switch (num) { - case 2: - result += 'It wriggled and jiggled and tickled inside her.\n' - break - case 3: - result += 'How absurd to swallow a bird!\n' - break - case 4: - result += 'Imagine that, to swallow a cat!\n' - break - case 5: - result += 'What a hog, to swallow a dog!\n' - break - case 6: - result += 'Just opened her throat and swallowed a goat!\n' - break - case 7: - result += "I don't know how she swallowed a cow!\n" - break - case 8: - result += "She's dead, of course!\n" - return result - default: - break - } - - while (index >= 1) { - result += `She swallowed the ${animals[index]} to catch the ${ - animals[index - 1] - }` - if (index === 2) { - result += ' that wriggled and jiggled and tickled inside her' - } - result += '.\n' - index -= 1 - } - result += "I don't know why she swallowed the fly. Perhaps she'll die." - - return result + '\n' - } -} diff --git a/exercises/practice/food-chain/food-chain.test.ts b/exercises/practice/food-chain/food-chain.test.ts index 4d60f1efa..f6acce6ae 100644 --- a/exercises/practice/food-chain/food-chain.test.ts +++ b/exercises/practice/food-chain/food-chain.test.ts @@ -1,4 +1,4 @@ -import FoodChain from './food-chain' +import { verse, verses } from './food-chain' describe('Food Chain', () => { it('fly', () => { @@ -6,7 +6,7 @@ describe('Food Chain', () => { I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(1)).toEqual(expected) + expect(verse(1)).toEqual(expected) }) xit('spider', () => { @@ -16,7 +16,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(2)).toEqual(expected) + expect(verse(2)).toEqual(expected) }) xit('bird', () => { @@ -27,7 +27,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(3)).toEqual(expected) + expect(verse(3)).toEqual(expected) }) xit('cat', () => { @@ -39,7 +39,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(4)).toEqual(expected) + expect(verse(4)).toEqual(expected) }) xit('dog', () => { @@ -52,7 +52,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(5)).toEqual(expected) + expect(verse(5)).toEqual(expected) }) xit('goat', () => { @@ -66,7 +66,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(6)).toEqual(expected) + expect(verse(6)).toEqual(expected) }) xit('cow', () => { @@ -81,7 +81,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verse(7)).toEqual(expected) + expect(verse(7)).toEqual(expected) }) xit('horse', () => { @@ -89,7 +89,7 @@ I don't know why she swallowed the fly. Perhaps she'll die. She's dead, of course! ` - expect(FoodChain.verse(8)).toEqual(expected) + expect(verse(8)).toEqual(expected) }) xit('multiple verses', () => { @@ -102,7 +102,7 @@ She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` - expect(FoodChain.verses(1, 2)).toEqual(expected) + expect(verses(1, 2)).toEqual(expected) }) xit('the whole song', () => { @@ -157,6 +157,6 @@ 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! ` - expect(FoodChain.verses(1, 8)).toEqual(expected) + expect(verses(1, 8)).toEqual(expected) }) }) diff --git a/exercises/practice/food-chain/food-chain.ts b/exercises/practice/food-chain/food-chain.ts index e69de29bb..fd5c6b17a 100644 --- a/exercises/practice/food-chain/food-chain.ts +++ b/exercises/practice/food-chain/food-chain.ts @@ -0,0 +1,7 @@ +export function verse(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') +} + +export function verses(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/gigasecond/gigasecond.example.ts b/exercises/practice/gigasecond/.meta/proof.ci.ts similarity index 84% rename from exercises/practice/gigasecond/gigasecond.example.ts rename to exercises/practice/gigasecond/.meta/proof.ci.ts index 3da61e3ab..a2780de57 100644 --- a/exercises/practice/gigasecond/gigasecond.example.ts +++ b/exercises/practice/gigasecond/.meta/proof.ci.ts @@ -1,6 +1,6 @@ const gigamilisecond = 10 ** 12 -export default class Gigasecond { +export class Gigasecond { constructor(private readonly currentDate: Readonly) {} public date(): Date { diff --git a/exercises/practice/gigasecond/gigasecond.test.ts b/exercises/practice/gigasecond/gigasecond.test.ts index d6fb10c25..47b9c392e 100644 --- a/exercises/practice/gigasecond/gigasecond.test.ts +++ b/exercises/practice/gigasecond/gigasecond.test.ts @@ -1,4 +1,4 @@ -import Gigasecond from './gigasecond' +import { Gigasecond } from './gigasecond' describe('Gigasecond', () => { it('tells a gigasecond anniversary since midnight', () => { diff --git a/exercises/practice/gigasecond/gigasecond.ts b/exercises/practice/gigasecond/gigasecond.ts index 7102044c5..2b42266cf 100644 --- a/exercises/practice/gigasecond/gigasecond.ts +++ b/exercises/practice/gigasecond/gigasecond.ts @@ -1,7 +1,5 @@ -class Gigasecond { - date(/* Parameters go here */) { - // Your code here +export class Gigasecond { + public date(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') } } - -export default Gigasecond diff --git a/exercises/practice/grade-school/grade-school.example.ts b/exercises/practice/grade-school/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/grade-school/grade-school.example.ts rename to exercises/practice/grade-school/.meta/proof.ci.ts diff --git a/exercises/practice/grade-school/grade-school.test.ts b/exercises/practice/grade-school/grade-school.test.ts index 50ffddf77..74ae0e3d6 100644 --- a/exercises/practice/grade-school/grade-school.test.ts +++ b/exercises/practice/grade-school/grade-school.test.ts @@ -1,92 +1,90 @@ -import GradeSchool from './grade-school' +import { GradeSchool } from './grade-school' describe('School', () => { - let gradeSchool: GradeSchool - - beforeEach(() => { - gradeSchool = new GradeSchool() - }) - - it('a new school has an empty roster', () => { - expect(gradeSchool.studentRoster().size === 0).toEqual(true) + test('a new school has an empty roster', () => { + const school = new GradeSchool() + expect(school.roster()).toEqual({}) }) - xit("A student can't be in two different grades", () => { - gradeSchool.addStudent('Aimee', 2) - gradeSchool.addStudent('Aimee', 1) + xtest('adding a student adds them to the roster for the given grade', () => { + const school = new GradeSchool() + school.add('Aimee', 2) - const emptyGrade = gradeSchool.studentsInGrade(2) - expect(emptyGrade).toEqual([]) + const expectedDb = { 2: ['Aimee'] } + expect(school.roster()).toEqual(expectedDb) }) - xit('adding a student adds them to the roster for the given grade', () => { - gradeSchool.addStudent('Aimee', 2) + xtest('adding more students to the same grade adds them to the roster', () => { + const school = new GradeSchool() + school.add('Blair', 2) + school.add('James', 2) + school.add('Paul', 2) - const expectedDb = new Map(Object.entries({ 2: ['Aimee'] })) - expect(gradeSchool.studentRoster()).toEqual(expectedDb) + const expectedDb = { 2: ['Blair', 'James', 'Paul'] } + expect(school.roster()).toEqual(expectedDb) }) - xit('adding more students to the same grade adds them to the roster', () => { - gradeSchool.addStudent('Blair', 2) - gradeSchool.addStudent('James', 2) - gradeSchool.addStudent('Paul', 2) + xtest('adding students to different grades adds them to the roster', () => { + const school = new GradeSchool() + school.add('Chelsea', 3) + school.add('Logan', 7) - const expectedDb = new Map( - Object.entries({ 2: ['Blair', 'James', 'Paul'] }) - ) - expect(gradeSchool.studentRoster()).toEqual(expectedDb) + const expectedDb = { 3: ['Chelsea'], 7: ['Logan'] } + expect(school.roster()).toEqual(expectedDb) }) - xit('adding students to different grades adds them to the roster', () => { - gradeSchool.addStudent('Chelsea', 3) - gradeSchool.addStudent('Logan', 7) + xtest('grade returns the students in that grade in alphabetical order', () => { + const school = new GradeSchool() + school.add('Franklin', 5) + school.add('Bradley', 5) + school.add('Jeff', 1) - const expectedDb = new Map(Object.entries({ 3: ['Chelsea'], 7: ['Logan'] })) - expect(gradeSchool.studentRoster()).toEqual(expectedDb) + const expectedStudents = ['Bradley', 'Franklin'] + expect(school.grade(5)).toEqual(expectedStudents) }) - xit('grade returns the students in that grade in alphabetical order', () => { - gradeSchool.addStudent('Franklin', 5) - gradeSchool.addStudent('Bradley', 5) - gradeSchool.addStudent('Jeff', 1) - - const expectedStudents = ['Bradley', 'Franklin'] - expect(gradeSchool.studentsInGrade(5)).toEqual(expectedStudents) + xtest('grade returns an empty array if there are no students in that grade', () => { + const school = new GradeSchool() + expect(school.grade(1)).toEqual([]) }) - xit('grade returns an empty array if there are no students in that grade', () => { - expect(gradeSchool.studentsInGrade(1)).toEqual([]) + xtest('the students names in each grade in the roster are sorted', () => { + const school = new GradeSchool() + school.add('Jennifer', 4) + school.add('Kareem', 6) + school.add('Christopher', 4) + school.add('Kyle', 3) + + const expectedSortedStudents = { + 3: ['Kyle'], + 4: ['Christopher', 'Jennifer'], + 6: ['Kareem'], + } + expect(school.roster()).toEqual(expectedSortedStudents) }) - xit('the students names in each grade in the roster are sorted', () => { - gradeSchool.addStudent('Jennifer', 4) - gradeSchool.addStudent('Kareem', 6) - gradeSchool.addStudent('Christopher', 4) - gradeSchool.addStudent('Kyle', 3) - - const expectedSortedStudents = new Map( - Object.entries({ - 3: ['Kyle'], - 4: ['Christopher', 'Jennifer'], - 6: ['Kareem'], - }) - ) - expect(gradeSchool.studentRoster()).toEqual(expectedSortedStudents) + xtest('roster cannot be modified outside of module', () => { + const school = new GradeSchool() + school.add('Aimee', 2) + const roster = school.roster() + roster[2].push('Oops.') + const expectedDb = { 2: ['Aimee'] } + expect(school.roster()).toEqual(expectedDb) }) - xit('roster cannot be modified outside of module', () => { - gradeSchool.addStudent('Aimee', 2) - const roster = gradeSchool.studentRoster() - const result = roster.get('2') || [] - result.push('Oops.') - const expectedDb = new Map(Object.entries({ 2: ['Aimee'] })) - expect(gradeSchool.studentRoster()).toEqual(expectedDb) + xtest('roster cannot be modified outside of module using grade()', () => { + const school = new GradeSchool() + school.add('Aimee', 2) + school.grade(2).push('Oops.') + const expectedDb = { 2: ['Aimee'] } + expect(school.roster()).toEqual(expectedDb) }) - xit('roster cannot be modified outside of module using studentsInGrade()', () => { - gradeSchool.addStudent('Aimee', 2) - gradeSchool.studentsInGrade(2).push('Oops.') - const expectedDb = new Map(Object.entries({ 2: ['Aimee'] })) - expect(gradeSchool.studentRoster()).toEqual(expectedDb) + xtest("a student can't be in two different grades", () => { + const school = new GradeSchool() + school.add('Aimee', 2) + school.add('Aimee', 1) + + expect(school.grade(2)).toEqual([]) }) }) diff --git a/exercises/practice/grade-school/grade-school.ts b/exercises/practice/grade-school/grade-school.ts index e69de29bb..45faf164d 100644 --- a/exercises/practice/grade-school/grade-school.ts +++ b/exercises/practice/grade-school/grade-school.ts @@ -0,0 +1,13 @@ +export class GradeSchool { + roster() { + throw new Error('Remove this statement and implement this function') + } + + add() { + throw new Error('Remove this statement and implement this function') + } + + grade() { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/grains/.meta/proof.ci.ts b/exercises/practice/grains/.meta/proof.ci.ts new file mode 100644 index 000000000..26b402b8b --- /dev/null +++ b/exercises/practice/grains/.meta/proof.ci.ts @@ -0,0 +1,17 @@ +export function square(square: number): number { + if (square <= 0 || square >= 65) { + throw new Error() + } + + return Math.pow(2, square - 1) +} + +export function total(): number { + let total = 0 + + for (let i = 1; i <= 64; i++) { + total += this.square(i) + } + + return total +} diff --git a/exercises/practice/grains/grains.example.ts b/exercises/practice/grains/grains.example.ts deleted file mode 100644 index 2459e63ca..000000000 --- a/exercises/practice/grains/grains.example.ts +++ /dev/null @@ -1,21 +0,0 @@ -class Grains { - public static square(square: number): number { - if (square <= 0 || square >= 65) { - throw new Error() - } - - return Math.pow(2, square - 1) - } - - public static total(): number { - let total = 0 - - for (let i = 1; i <= 64; i++) { - total += this.square(i) - } - - return total - } -} - -export default Grains diff --git a/exercises/practice/grains/grains.test.ts b/exercises/practice/grains/grains.test.ts index b97997919..abc4c42a2 100644 --- a/exercises/practice/grains/grains.test.ts +++ b/exercises/practice/grains/grains.test.ts @@ -1,57 +1,57 @@ -import Grains from './grains' +import { square, total } from './grains' describe('returns the number of grains on the square', () => { it('1', () => { const expected = 1 - expect(Grains.square(1)).toEqual(expected) + expect(square(1)).toEqual(expected) }) xit('2', () => { const expected = 2 - expect(Grains.square(2)).toEqual(expected) + expect(square(2)).toEqual(expected) }) xit('3', () => { const expected = 4 - expect(Grains.square(3)).toEqual(expected) + expect(square(3)).toEqual(expected) }) xit('4', () => { const expected = 8 - expect(Grains.square(4)).toEqual(expected) + expect(square(4)).toEqual(expected) }) xit('16', () => { const expected = 32768 - expect(Grains.square(16)).toEqual(expected) + expect(square(16)).toEqual(expected) }) xit('32', () => { const expected = 2147483648 - expect(Grains.square(32)).toEqual(expected) + expect(square(32)).toEqual(expected) }) xit('64', () => { const expected = 9223372036854775808 - expect(Grains.square(64)).toEqual(expected) + expect(square(64)).toEqual(expected) }) xit('square 0 raises an exception', () => { - expect(() => Grains.square(0)).toThrow() + expect(() => square(0)).toThrow() }) xit('negative square raises an exception', () => { - expect(() => Grains.square(-1)).toThrow() + expect(() => square(-1)).toThrow() }) xit('square greater than 64 raises an exception', () => { - expect(() => Grains.square(65)).toThrow() + expect(() => square(65)).toThrow() }) }) describe('returns the total number of grains on the board', () => { xit('total', () => { const expected = 18446744073709551615 - expect(Grains.total()).toEqual(expected) + expect(total()).toEqual(expected) }) }) diff --git a/exercises/practice/grains/grains.ts b/exercises/practice/grains/grains.ts index e69de29bb..9dd17d62d 100644 --- a/exercises/practice/grains/grains.ts +++ b/exercises/practice/grains/grains.ts @@ -0,0 +1,7 @@ +export const square = () => { + throw new Error('Remove this statement and implement this function') +} + +export const total = () => { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/hamming/.meta/proof.ci.ts b/exercises/practice/hamming/.meta/proof.ci.ts new file mode 100644 index 000000000..fea043b50 --- /dev/null +++ b/exercises/practice/hamming/.meta/proof.ci.ts @@ -0,0 +1,15 @@ +export function compute(left: string, right: string): number { + if (left.length !== right.length) { + throw new Error('DNA strands must be of equal length.') + } + const size = left.length + const leftArray = left.split('') + const rightArray = right.split('') + let count = 0 + for (let i = 0; i < size; i += 1) { + if (leftArray[i] !== rightArray[i]) { + count += 1 + } + } + return count +} diff --git a/exercises/practice/hamming/hamming.example.ts b/exercises/practice/hamming/hamming.example.ts deleted file mode 100644 index bb1b10264..000000000 --- a/exercises/practice/hamming/hamming.example.ts +++ /dev/null @@ -1,19 +0,0 @@ -class Hamming { - public compute(left: string, right: string): number { - if (left.length !== right.length) { - throw new Error('DNA strands must be of equal length.') - } - const size = left.length - const leftArray = left.split('') - const rightArray = right.split('') - let count = 0 - for (let i = 0; i < size; i += 1) { - if (leftArray[i] !== rightArray[i]) { - count += 1 - } - } - return count - } -} - -export default Hamming diff --git a/exercises/practice/hamming/hamming.test.ts b/exercises/practice/hamming/hamming.test.ts index 47c92921f..5b4dbd220 100644 --- a/exercises/practice/hamming/hamming.test.ts +++ b/exercises/practice/hamming/hamming.test.ts @@ -1,39 +1,37 @@ -import Hamming from './hamming' +import { compute } from './hamming' describe('Hamming', () => { - const hamming = new Hamming() - it('no difference between identical strands', () => { - expect(hamming.compute('A', 'A')).toEqual(0) + expect(compute('A', 'A')).toEqual(0) }) xit('complete hamming distance for single nucleotide strand', () => { - expect(hamming.compute('A', 'G')).toEqual(1) + expect(compute('A', 'G')).toEqual(1) }) xit('complete hamming distance for small strand', () => { - expect(hamming.compute('AG', 'CT')).toEqual(2) + expect(compute('AG', 'CT')).toEqual(2) }) xit('small hamming distance', () => { - expect(hamming.compute('AT', 'CT')).toEqual(1) + expect(compute('AT', 'CT')).toEqual(1) }) xit('small hamming distance in longer strand', () => { - expect(hamming.compute('GGACG', 'GGTCG')).toEqual(1) + expect(compute('GGACG', 'GGTCG')).toEqual(1) }) xit('large hamming distance', () => { - expect(hamming.compute('GATACA', 'GCATAA')).toEqual(4) + expect(compute('GATACA', 'GCATAA')).toEqual(4) }) xit('hamming distance in very long strand', () => { - expect(hamming.compute('GGACGGATTCTG', 'AGGACGGATTCT')).toEqual(9) + expect(compute('GGACGGATTCTG', 'AGGACGGATTCT')).toEqual(9) }) xit('throws error when strands are not equal length', () => { expect(() => { - hamming.compute('GGACGGATTCTG', 'AGGAC') + compute('GGACGGATTCTG', 'AGGAC') }).toThrowError('DNA strands must be of equal length.') }) }) diff --git a/exercises/practice/hamming/hamming.ts b/exercises/practice/hamming/hamming.ts index e69de29bb..6e0233c04 100644 --- a/exercises/practice/hamming/hamming.ts +++ b/exercises/practice/hamming/hamming.ts @@ -0,0 +1,3 @@ +export function compute(left: unknown, right: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/hello-world/.meta/proof.ci.ts b/exercises/practice/hello-world/.meta/proof.ci.ts new file mode 100644 index 000000000..c6c845b44 --- /dev/null +++ b/exercises/practice/hello-world/.meta/proof.ci.ts @@ -0,0 +1,3 @@ +export function hello(): string { + return 'Hello, World!' +} diff --git a/exercises/practice/hello-world/hello-world.example.ts b/exercises/practice/hello-world/hello-world.example.ts deleted file mode 100644 index 3f85bb5e2..000000000 --- a/exercises/practice/hello-world/hello-world.example.ts +++ /dev/null @@ -1,7 +0,0 @@ -class HelloWorld { - public static hello(): string { - return 'Hello, World!' - } -} - -export default HelloWorld diff --git a/exercises/practice/hello-world/hello-world.test.ts b/exercises/practice/hello-world/hello-world.test.ts index bebad55c4..23bf860da 100644 --- a/exercises/practice/hello-world/hello-world.test.ts +++ b/exercises/practice/hello-world/hello-world.test.ts @@ -1,7 +1,7 @@ -import HelloWorld from './hello-world' +import { hello } from './hello-world' describe('Hello World', () => { it('says hello world with no name', () => { - expect(HelloWorld.hello()).toEqual('Hello, World!') + expect(hello()).toEqual('Hello, World!') }) }) diff --git a/exercises/practice/hello-world/hello-world.ts b/exercises/practice/hello-world/hello-world.ts index a914df052..59c1da902 100644 --- a/exercises/practice/hello-world/hello-world.ts +++ b/exercises/practice/hello-world/hello-world.ts @@ -1,7 +1,3 @@ -class HelloWorld { - static hello() { - // Your code here - } +export function hello(): string { + return "What's up doc 👋🏽?" } - -export default HelloWorld diff --git a/exercises/practice/house/.meta/proof.ci.ts b/exercises/practice/house/.meta/proof.ci.ts new file mode 100644 index 000000000..a1f65c979 --- /dev/null +++ b/exercises/practice/house/.meta/proof.ci.ts @@ -0,0 +1,64 @@ +const OBJECTS = [ + 'house', + 'malt', + 'rat', + 'cat', + 'dog', + 'cow with the crumpled horn', + 'maiden all forlorn', + 'man all tattered and torn', + 'priest all shaven and shorn', + 'rooster that crowed in the morn', + 'farmer sowing his corn', + 'horse and the hound and the horn', +] as const + +const ACTIONS = [ + 'Jack built', + 'lay in', + 'ate', + 'killed', + 'worried', + 'tossed', + 'milked', + 'kissed', + 'married', + 'woke', + 'kept', + 'belonged to', +] as const + +export function verse(verseNumber: number): string[] { + const lines = [] + const totalLines = verseNumber + let itemIndex = verseNumber - 1 + for (let lineNumber = 1; lineNumber <= totalLines; lineNumber += 1) { + let lineText = '' + if (lineNumber === 1) { + lineText += 'This is' + } else { + lineText += `that ${ACTIONS[itemIndex]}` + itemIndex -= 1 + } + + lineText += ` the ${OBJECTS[itemIndex]}` + if (lineNumber === totalLines) { + lineText += ` that ${ACTIONS[itemIndex]}.` + } + lines.push(lineText) + } + + return lines +} + +export function verses(start: number, end: number): string[] { + let lines: string[] = [] + for (let i = start; i <= end; i += 1) { + const verseLines = verse(i) + lines = lines.concat(verseLines) + if (i < end) { + lines.push('') + } + } + return lines +} diff --git a/exercises/practice/house/house.example.ts b/exercises/practice/house/house.example.ts deleted file mode 100644 index 526dc218a..000000000 --- a/exercises/practice/house/house.example.ts +++ /dev/null @@ -1,68 +0,0 @@ -const OBJECTS = [ - 'house', - 'malt', - 'rat', - 'cat', - 'dog', - 'cow with the crumpled horn', - 'maiden all forlorn', - 'man all tattered and torn', - 'priest all shaven and shorn', - 'rooster that crowed in the morn', - 'farmer sowing his corn', - 'horse and the hound and the horn', -] as const - -const ACTIONS = [ - 'Jack built', - 'lay in', - 'ate', - 'killed', - 'worried', - 'tossed', - 'milked', - 'kissed', - 'married', - 'woke', - 'kept', - 'belonged to', -] as const - -class House { - public static verse(verseNumber: number): string[] { - const lines = [] - const totalLines = verseNumber - let itemIndex = verseNumber - 1 - for (let lineNumber = 1; lineNumber <= totalLines; lineNumber += 1) { - let lineText = '' - if (lineNumber === 1) { - lineText += 'This is' - } else { - lineText += `that ${ACTIONS[itemIndex]}` - itemIndex -= 1 - } - - lineText += ` the ${OBJECTS[itemIndex]}` - if (lineNumber === totalLines) { - lineText += ` that ${ACTIONS[itemIndex]}.` - } - lines.push(lineText) - } - - return lines - } - - public static verses(start: number, end: number): string[] { - let lines: string[] = [] - for (let i = start; i <= end; i += 1) { - const verseLines = House.verse(i) - lines = lines.concat(verseLines) - if (i < end) { - lines.push('') - } - } - return lines - } -} - -export default House diff --git a/exercises/practice/house/house.test.ts b/exercises/practice/house/house.test.ts index fb9ccafbd..a01af3c5d 100644 --- a/exercises/practice/house/house.test.ts +++ b/exercises/practice/house/house.test.ts @@ -1,9 +1,9 @@ -import House from './house' +import { verse, verses } from './house' describe('House', () => { it('verse one - the house that jack built', () => { const lyrics = ['This is the house that Jack built.'] - expect(House.verse(1)).toEqual(lyrics) + expect(verse(1)).toEqual(lyrics) }) xit('verse two - the malt that lay', () => { @@ -11,7 +11,7 @@ describe('House', () => { 'This is the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(2)).toEqual(lyrics) + expect(verse(2)).toEqual(lyrics) }) xit('verse three - the rat that ate', () => { @@ -20,7 +20,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(3)).toEqual(lyrics) + expect(verse(3)).toEqual(lyrics) }) xit('verse four - the cat that killed', () => { @@ -30,7 +30,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(4)).toEqual(lyrics) + expect(verse(4)).toEqual(lyrics) }) xit('verse five - the dog that worried', () => { @@ -41,7 +41,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(5)).toEqual(lyrics) + expect(verse(5)).toEqual(lyrics) }) xit('verse six - the cow with the crumpled horn', () => { @@ -53,7 +53,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(6)).toEqual(lyrics) + expect(verse(6)).toEqual(lyrics) }) xit('verse seven - the maiden all forlorn', () => { @@ -66,7 +66,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(7)).toEqual(lyrics) + expect(verse(7)).toEqual(lyrics) }) xit('verse eight - the man all tattered and torn', () => { @@ -80,7 +80,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(8)).toEqual(lyrics) + expect(verse(8)).toEqual(lyrics) }) xit('verse nine - the priest all shaven and shorn', () => { @@ -95,7 +95,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(9)).toEqual(lyrics) + expect(verse(9)).toEqual(lyrics) }) xit('verse ten - the rooster that crowed in the morn', () => { @@ -111,7 +111,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(10)).toEqual(lyrics) + expect(verse(10)).toEqual(lyrics) }) xit('verse eleven - the farmer sowing his corn', () => { @@ -128,7 +128,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(11)).toEqual(lyrics) + expect(verse(11)).toEqual(lyrics) }) xit('verse twelve - the horse and the hound and the horn', () => { @@ -146,7 +146,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verse(12)).toEqual(lyrics) + expect(verse(12)).toEqual(lyrics) }) xit('multiple verses', () => { @@ -188,7 +188,7 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verses(startVerse, endVerse)).toEqual(lyrics) + expect(verses(startVerse, endVerse)).toEqual(lyrics) }) xit('full rhyme', () => { @@ -285,6 +285,6 @@ describe('House', () => { 'that ate the malt', 'that lay in the house that Jack built.', ] - expect(House.verses(startVerse, endVerse)).toEqual(lyrics) + expect(verses(startVerse, endVerse)).toEqual(lyrics) }) }) diff --git a/exercises/practice/house/house.ts b/exercises/practice/house/house.ts index e69de29bb..48b0c15ef 100644 --- a/exercises/practice/house/house.ts +++ b/exercises/practice/house/house.ts @@ -0,0 +1,7 @@ +export function verse(/* parameters go here */): unknown { + throw new Error('Remove this statement and implement this function') +} + +export function verses(/* parameters go here */): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/isbn-verifier/.meta/proof.ci.ts b/exercises/practice/isbn-verifier/.meta/proof.ci.ts new file mode 100644 index 000000000..0767a7006 --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/proof.ci.ts @@ -0,0 +1,18 @@ +export function isValid(input: string): boolean { + const isbn = input.replace(/-/g, '') + if (!isbn.match(/^(\d{9}[\dxX])$/)) { + return false + } + + const digits = [...isbn] + if (digits[9].match(/[xX]/)) { + digits[9] = '10' + } + + const sum = digits.reduce( + (acc, value, index) => acc + (10 - index) * parseInt(value, 10), + 0 + ) + + return sum % 11 === 0 +} diff --git a/exercises/practice/isbn-verifier/isbn-verifier.example.ts b/exercises/practice/isbn-verifier/isbn-verifier.example.ts deleted file mode 100644 index ae51e010c..000000000 --- a/exercises/practice/isbn-verifier/isbn-verifier.example.ts +++ /dev/null @@ -1,27 +0,0 @@ -class ISBN { - private readonly isbn: string - - constructor(isbn: string) { - this.isbn = isbn.replace(/-/g, '') - } - - public isValid(): boolean { - if (!this.isbn.match(/^(\d{9}[\dxX])$/)) { - return false - } - - const digits = [...this.isbn] - if (digits[9].match(/[xX]/)) { - digits[9] = '10' - } - - const sum = digits.reduce( - (acc, value, index) => acc + (10 - index) * parseInt(value, 10), - 0 - ) - - return sum % 11 === 0 - } -} - -export default ISBN diff --git a/exercises/practice/isbn-verifier/isbn-verifier.test.ts b/exercises/practice/isbn-verifier/isbn-verifier.test.ts index cf810de44..53e646477 100644 --- a/exercises/practice/isbn-verifier/isbn-verifier.test.ts +++ b/exercises/practice/isbn-verifier/isbn-verifier.test.ts @@ -1,59 +1,59 @@ -import ISBN from './isbn-verifier' +import { isValid } from './isbn-verifier' describe('ISBN Verifier', () => { it('valid isbn number', () => { - expect(new ISBN('3-598-21508-8').isValid()).toBeTruthy() + expect(isValid('3-598-21508-8')).toBeTruthy() }) xit('invalid isbn check digit', () => { - expect(new ISBN('3-598-21508-9').isValid()).toBeFalsy() + expect(isValid('3-598-21508-9')).toBeFalsy() }) xit('valid isbn number with a check digit of 10', () => { - expect(new ISBN('3-598-21507-X').isValid()).toBeTruthy() + expect(isValid('3-598-21507-X')).toBeTruthy() }) xit('check digit is a character other than X', () => { - expect(new ISBN('3-598-21507-A').isValid()).toBeFalsy() + expect(isValid('3-598-21507-A')).toBeFalsy() }) xit('invalid character in isbn', () => { - expect(new ISBN('3-598-2K507-0').isValid()).toBeFalsy() + expect(isValid('3-598-2K507-0')).toBeFalsy() }) xit('X is only valid as a check digit', () => { - expect(new ISBN('3-598-2X507-9').isValid()).toBeFalsy() + expect(isValid('3-598-2X507-9')).toBeFalsy() }) xit('valid isbn without separating dashes', () => { - expect(new ISBN('3598215088').isValid()).toBeTruthy() + expect(isValid('3598215088')).toBeTruthy() }) xit('isbn without separating dashes and X as check digit', () => { - expect(new ISBN('359821507X').isValid()).toBeTruthy() + expect(isValid('359821507X')).toBeTruthy() }) xit('isbn without check digit and dashes', () => { - expect(new ISBN('359821507').isValid()).toBeFalsy() + expect(isValid('359821507')).toBeFalsy() }) xit('too long isbn and no dashes', () => { - expect(new ISBN('3598215078X').isValid()).toBeFalsy() + expect(isValid('3598215078X')).toBeFalsy() }) xit('isbn without check digit', () => { - expect(new ISBN('3-598-21507').isValid()).toBeFalsy() + expect(isValid('3-598-21507')).toBeFalsy() }) xit('too long isbn', () => { - expect(new ISBN('3-598-21507-XX').isValid()).toBeFalsy() + expect(isValid('3-598-21507-XX')).toBeFalsy() }) xit('check digit of X should not be used for 0', () => { - expect(new ISBN('3-598-21515-X').isValid()).toBeFalsy() + expect(isValid('3-598-21515-X')).toBeFalsy() }) xit('empty isbn', () => { - expect(new ISBN('').isValid()).toBeFalsy() + expect(isValid('')).toBeFalsy() }) }) diff --git a/exercises/practice/isbn-verifier/isbn-verifier.ts b/exercises/practice/isbn-verifier/isbn-verifier.ts index e69de29bb..3251d9598 100644 --- a/exercises/practice/isbn-verifier/isbn-verifier.ts +++ b/exercises/practice/isbn-verifier/isbn-verifier.ts @@ -0,0 +1,3 @@ +export function isValid(isbn: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/isogram/.meta/proof.ci.ts b/exercises/practice/isogram/.meta/proof.ci.ts new file mode 100644 index 000000000..115ec4d23 --- /dev/null +++ b/exercises/practice/isogram/.meta/proof.ci.ts @@ -0,0 +1,28 @@ +export function isIsogram(phrase: string): boolean { + const appearances = new Set() + + for (const letter of phrase) { + if (isLetter(letter)) { + if (appearances.has(letter.toLowerCase())) { + return false + } else { + appearances.add(letter.toLowerCase()) + } + } else { + continue + } + } + + return true +} + +function isLetter(letter: string): boolean { + if ( + (letter.charCodeAt(0) >= 97 && letter.charCodeAt(0) <= 122) || + (letter.charCodeAt(0) >= 65 && letter.charCodeAt(0) <= 90) + ) { + return true + } else { + return false + } +} diff --git a/exercises/practice/isogram/isogram.example.ts b/exercises/practice/isogram/isogram.example.ts deleted file mode 100644 index 1407260f4..000000000 --- a/exercises/practice/isogram/isogram.example.ts +++ /dev/null @@ -1,34 +0,0 @@ -class Isogram { - private static alphabet = 'abcdefghijklmnopqrstuvwxyz' - - public static isIsogram(phrase: string): boolean { - const appearances = new Set() - - for (const letter of phrase) { - if (this.isLetter(letter)) { - if (appearances.has(letter.toLowerCase())) { - return false - } else { - appearances.add(letter.toLowerCase()) - } - } else { - continue - } - } - - return true - } - - private static isLetter(letter: string): boolean { - if ( - (letter.charCodeAt(0) >= 97 && letter.charCodeAt(0) <= 122) || - (letter.charCodeAt(0) >= 65 && letter.charCodeAt(0) <= 90) - ) { - return true - } else { - return false - } - } -} - -export default Isogram diff --git a/exercises/practice/isogram/isogram.test.ts b/exercises/practice/isogram/isogram.test.ts index 01dccdb5c..3cdf31066 100644 --- a/exercises/practice/isogram/isogram.test.ts +++ b/exercises/practice/isogram/isogram.test.ts @@ -1,48 +1,48 @@ -import Isogram from './isogram' +import { isIsogram } from './isogram' describe('Check if the given string is an isogram', () => { it('empty string', () => { const expected = true - expect(Isogram.isIsogram('')).toEqual(expected) + expect(isIsogram('')).toEqual(expected) }) xit('isogram with only lower case characters', () => { const expected = true - expect(Isogram.isIsogram('isogram')).toEqual(expected) + expect(isIsogram('isogram')).toEqual(expected) }) xit('word with one duplicated character', () => { const expected = false - expect(Isogram.isIsogram('eleven')).toEqual(expected) + expect(isIsogram('eleven')).toEqual(expected) }) xit('longest reported english isogram', () => { const expected = true - expect(Isogram.isIsogram('subdermatoglyphic')).toEqual(expected) + expect(isIsogram('subdermatoglyphic')).toEqual(expected) }) xit('word with duplicated character in mixed case', () => { const expected = false - expect(Isogram.isIsogram('Alphabet')).toEqual(expected) + expect(isIsogram('Alphabet')).toEqual(expected) }) xit('hypothetical isogrammic word with hyphen', () => { const expected = true - expect(Isogram.isIsogram('thumbscrew-japingly')).toEqual(expected) + expect(isIsogram('thumbscrew-japingly')).toEqual(expected) }) xit('isogram with duplicated hyphen', () => { const expected = true - expect(Isogram.isIsogram('six-year-old')).toEqual(expected) + expect(isIsogram('six-year-old')).toEqual(expected) }) xit('made-up name that is an isogram', () => { const expected = true - expect(Isogram.isIsogram('Emily Jung Schwartzkopf')).toEqual(expected) + expect(isIsogram('Emily Jung Schwartzkopf')).toEqual(expected) }) xit('duplicated character in the middle', () => { const expected = false - expect(Isogram.isIsogram('accentor')).toEqual(expected) + expect(isIsogram('accentor')).toEqual(expected) }) }) diff --git a/exercises/practice/isogram/isogram.ts b/exercises/practice/isogram/isogram.ts index f7f9e122f..4aeecf5ec 100644 --- a/exercises/practice/isogram/isogram.ts +++ b/exercises/practice/isogram/isogram.ts @@ -1,7 +1,3 @@ -class Isogram { - static isIsogram(/* Parameters go here */) { - // Your code here - } +export function isIsogram(/* parameters go here */): unknown { + throw new Error('Remove this statement and implement this function') } - -export default Isogram diff --git a/exercises/practice/kindergarten-garden/.meta/proof.ci.ts b/exercises/practice/kindergarten-garden/.meta/proof.ci.ts new file mode 100644 index 000000000..df1745e63 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/proof.ci.ts @@ -0,0 +1,60 @@ +const DEFAULT_STUDENTS: Student[] = [ + 'Alice', + 'Bob', + 'Charlie', + 'David', + 'Eve', + 'Fred', + 'Ginny', + 'Harriet', + 'Ileana', + 'Joseph', + 'Kincaid', + 'Larry', +] + +const PLANT_CODES = { + G: 'grass', + V: 'violets', + R: 'radishes', + C: 'clover', +} as const + +type Student = string +type Plant = typeof PLANT_CODES[keyof typeof PLANT_CODES] +type Plants = Plant[] +type Pots = Plants[] + +function getPlants(pots: Pots, index: number): Plants { + const plants = [] + const position = 2 * index + plants.push(pots[0][position]) + plants.push(pots[0][position + 1]) + plants.push(pots[1][position]) + plants.push(pots[1][position + 1]) + return plants +} + +function parse(diagram: string): Pots { + return diagram + .split('\n') + .map((row) => [...row].map((sign) => PLANT_CODES[sign])) +} + +export class Garden { + private plots: Record + + constructor(diagram: string, private students = DEFAULT_STUDENTS) { + this.students.sort() + + this.plots = {} + + this.students.forEach((student, index) => { + this.plots[student] = getPlants(parse(diagram), index) + }) + } + + public plants(student: Student): Plants { + return this.plots[student] + } +} diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden.example.ts b/exercises/practice/kindergarten-garden/kindergarten-garden.example.ts deleted file mode 100644 index 945e8ac71..000000000 --- a/exercises/practice/kindergarten-garden/kindergarten-garden.example.ts +++ /dev/null @@ -1,68 +0,0 @@ -const defaultChildren = [ - 'Alice', - 'Bob', - 'Charlie', - 'David', - 'Eve', - 'Fred', - 'Ginny', - 'Harriet', - 'Ileana', - 'Joseph', - 'Kincaid', - 'Larry', -] - -interface Plants { - [code: string]: string -} - -const plants: Plants = { - G: 'grass', - V: 'violets', - R: 'radishes', - C: 'clover', -} - -interface Pots { - upper: string[] - lower: string[] -} - -const converToPots = (pots: string[][]): Pots => { - return { - upper: pots[0], - lower: pots[1], - } -} - -const getPlants = (pots: Pots, index: number): string[] => { - const plants = [] - const position = 2 * index - plants.push(pots.upper[position]) - plants.push(pots.upper[position + 1]) - plants.push(pots.lower[position]) - plants.push(pots.lower[position + 1]) - return plants -} - -const parse = (diagram: string): string[][] => { - return diagram.split('\n').map((row) => [...row].map((sign) => plants[sign])) -} - -export default class Garden { - private students: string[]; - [student: string]: string[] - - constructor(diagrams: string, students?: string[]) { - this.students = students || defaultChildren - this.students.sort() - - this.students.forEach((student, index) => { - this[student.toLocaleLowerCase()] = getPlants( - converToPots(parse(diagrams)), - index - ) - }) - } -} diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden.test.ts b/exercises/practice/kindergarten-garden/kindergarten-garden.test.ts index 135672a25..50a6f5323 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden.test.ts +++ b/exercises/practice/kindergarten-garden/kindergarten-garden.test.ts @@ -1,8 +1,8 @@ -import Garden from './kindergarten-garden' +import { Garden } from './kindergarten-garden' -describe('Garden', () => { - it('for Alice', () => { - expect(new Garden('RC\nGG').alice).toEqual([ +describe('partial Garden', () => { + test('garden with single student', () => { + expect(new Garden('RC\nGG').plants('Alice')).toEqual([ 'radishes', 'clover', 'grass', @@ -10,8 +10,8 @@ describe('Garden', () => { ]) }) - xit('another for Alice', () => { - expect(new Garden('VC\nRC').alice).toEqual([ + xtest('different garden with single student', () => { + expect(new Garden('VC\nRC').plants('Alice')).toEqual([ 'violets', 'clover', 'radishes', @@ -19,8 +19,8 @@ describe('Garden', () => { ]) }) - xit('for Bob', () => { - expect(new Garden('VVCG\nVVRC').bob).toEqual([ + xtest('garden with two students', () => { + expect(new Garden('VVCG\nVVRC').plants('Bob')).toEqual([ 'clover', 'grass', 'radishes', @@ -28,47 +28,96 @@ describe('Garden', () => { ]) }) - xit('for Bob and Charlie', () => { - const garden = new Garden('VVCCGG\nVVCCGG') - expect(garden.bob).toEqual(['clover', 'clover', 'clover', 'clover']) - expect(garden.charlie).toEqual(['grass', 'grass', 'grass', 'grass']) + describe('multiple students for the same garden with three students', () => { + xtest("second student's garden", () => { + expect(new Garden('VVCCGG\nVVCCGG').plants('Bob')).toEqual([ + 'clover', + 'clover', + 'clover', + 'clover', + ]) + }) + + xtest("third student's garden", () => { + expect(new Garden('VVCCGG\nVVCCGG').plants('Charlie')).toEqual([ + 'grass', + 'grass', + 'grass', + 'grass', + ]) + }) }) }) -describe('Full garden', () => { +describe('full garden', () => { const diagram = 'VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV' const garden = new Garden(diagram) - xit('for Alice', () => { - expect(garden.alice).toEqual(['violets', 'radishes', 'violets', 'radishes']) + xtest("for Alice, first student's garden", () => { + expect(garden.plants('Alice')).toEqual([ + 'violets', + 'radishes', + 'violets', + 'radishes', + ]) }) - xit('for Bob', () => { - expect(garden.bob).toEqual(['clover', 'grass', 'clover', 'clover']) + xtest("for Bob, second student's garden", () => { + expect(garden.plants('Bob')).toEqual([ + 'clover', + 'grass', + 'clover', + 'clover', + ]) }) - xit('for Charlie', () => { - expect(garden.charlie).toEqual(['violets', 'violets', 'clover', 'grass']) + xtest('for Charlie', () => { + expect(garden.plants('Charlie')).toEqual([ + 'violets', + 'violets', + 'clover', + 'grass', + ]) }) - xit('for David', () => { - expect(garden.david).toEqual(['radishes', 'violets', 'clover', 'radishes']) + xtest('for David', () => { + expect(garden.plants('David')).toEqual([ + 'radishes', + 'violets', + 'clover', + 'radishes', + ]) }) - xit('for Eve', () => { - expect(garden.eve).toEqual(['clover', 'grass', 'radishes', 'grass']) + xtest('for Eve', () => { + expect(garden.plants('Eve')).toEqual([ + 'clover', + 'grass', + 'radishes', + 'grass', + ]) }) - xit('for Fred', () => { - expect(garden.fred).toEqual(['grass', 'clover', 'violets', 'clover']) + xtest('for Fred', () => { + expect(garden.plants('Fred')).toEqual([ + 'grass', + 'clover', + 'violets', + 'clover', + ]) }) - xit('for Ginny', () => { - expect(garden.ginny).toEqual(['clover', 'grass', 'grass', 'clover']) + xtest('for Ginny', () => { + expect(garden.plants('Ginny')).toEqual([ + 'clover', + 'grass', + 'grass', + 'clover', + ]) }) - xit('for Harriet', () => { - expect(garden.harriet).toEqual([ + xtest('for Harriet', () => { + expect(garden.plants('Harriet')).toEqual([ 'violets', 'radishes', 'radishes', @@ -76,30 +125,50 @@ describe('Full garden', () => { ]) }) - xit('for Ileana', () => { - expect(garden.ileana).toEqual(['grass', 'clover', 'violets', 'clover']) + xtest('for Ileana', () => { + expect(garden.plants('Ileana')).toEqual([ + 'grass', + 'clover', + 'violets', + 'clover', + ]) }) - xit('for Joseph', () => { - expect(garden.joseph).toEqual(['violets', 'clover', 'violets', 'grass']) + xtest('for Joseph', () => { + expect(garden.plants('Joseph')).toEqual([ + 'violets', + 'clover', + 'violets', + 'grass', + ]) }) - xit('for Kincaid', () => { - expect(garden.kincaid).toEqual(['grass', 'clover', 'clover', 'grass']) + xtest("for Kincaid, second to last student's garden", () => { + expect(garden.plants('Kincaid')).toEqual([ + 'grass', + 'clover', + 'clover', + 'grass', + ]) }) - xit('for Larry', () => { - expect(garden.larry).toEqual(['grass', 'violets', 'clover', 'violets']) + xtest("for Larry, last student's garden", () => { + expect(garden.plants('Larry')).toEqual([ + 'grass', + 'violets', + 'clover', + 'violets', + ]) }) }) -describe('Disordered class', () => { +describe('disordered class', () => { const diagram = 'VCRRGVRG\nRVGCCGCV' const students = ['Samantha', 'Patricia', 'Xander', 'Roger'] const garden = new Garden(diagram, students) - xit('Patricia', () => { - expect(garden.patricia).toEqual([ + xtest('for Patricia', () => { + expect(garden.plants('Patricia')).toEqual([ 'violets', 'clover', 'radishes', @@ -107,16 +176,31 @@ describe('Disordered class', () => { ]) }) - xit('Roger', () => { - expect(garden.roger).toEqual(['radishes', 'radishes', 'grass', 'clover']) + xtest('for Roger', () => { + expect(garden.plants('Roger')).toEqual([ + 'radishes', + 'radishes', + 'grass', + 'clover', + ]) }) - xit('Samantha', () => { - expect(garden.samantha).toEqual(['grass', 'violets', 'clover', 'grass']) + xtest('for Samantha', () => { + expect(garden.plants('Samantha')).toEqual([ + 'grass', + 'violets', + 'clover', + 'grass', + ]) }) - xit('Xander', () => { - expect(garden.xander).toEqual(['radishes', 'grass', 'clover', 'violets']) + xtest('for Xander', () => { + expect(garden.plants('Xander')).toEqual([ + 'radishes', + 'grass', + 'clover', + 'violets', + ]) }) }) @@ -125,10 +209,30 @@ describe('Two gardens, different students', () => { const garden1 = new Garden(diagram, ['Alice', 'Bob', 'Charlie', 'Dan']) const garden2 = new Garden(diagram, ['Bob', 'Charlie', 'Dan', 'Erin']) - xit('Bob and Charlie for each garden', () => { - expect(garden1.bob).toEqual(['radishes', 'radishes', 'grass', 'clover']) - expect(garden2.bob).toEqual(['violets', 'clover', 'radishes', 'violets']) - expect(garden1.charlie).toEqual(['grass', 'violets', 'clover', 'grass']) - expect(garden2.charlie).toEqual(['radishes', 'radishes', 'grass', 'clover']) + xtest('Bob and Charlie for each garden', () => { + expect(garden1.plants('Bob')).toEqual([ + 'radishes', + 'radishes', + 'grass', + 'clover', + ]) + expect(garden2.plants('Bob')).toEqual([ + 'violets', + 'clover', + 'radishes', + 'violets', + ]) + expect(garden1.plants('Charlie')).toEqual([ + 'grass', + 'violets', + 'clover', + 'grass', + ]) + expect(garden2.plants('Charlie')).toEqual([ + 'radishes', + 'radishes', + 'grass', + 'clover', + ]) }) }) diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden.ts b/exercises/practice/kindergarten-garden/kindergarten-garden.ts index e69de29bb..df71c0f50 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden.ts +++ b/exercises/practice/kindergarten-garden/kindergarten-garden.ts @@ -0,0 +1,41 @@ +// +// This is only a SKELETON file for the 'Kindergarten Garden' exercise. +// It's been provided as a convenience to get you started writing code faster. +// + +const DEFAULT_STUDENTS: Student[] = [ + 'Alice', + 'Bob', + 'Charlie', + 'David', + 'Eve', + 'Fred', + 'Ginny', + 'Harriet', + 'Ileana', + 'Joseph', + 'Kincaid', + 'Larry', +] + +const PLANT_CODES = { + G: 'grass', + V: 'violets', + R: 'radishes', + C: 'clover', +} as const + +type Student = string +type Plant = typeof PLANT_CODES[keyof typeof PLANT_CODES] +type Plants = Plant[] +type Pots = Plants[] + +export class Garden { + constructor(diagram: string, students = DEFAULT_STUDENTS) { + throw new Error('Remove this statement and implement this function') + } + + public plants(student: Student): Plants { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/largest-series-product/.meta/proof.ci.ts b/exercises/practice/largest-series-product/.meta/proof.ci.ts new file mode 100644 index 000000000..06f2d65d9 --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/proof.ci.ts @@ -0,0 +1,28 @@ +export const largestProduct = (digits: string, seriesLength: number) => { + if (seriesLength === 0) { + return 1 + } + if (seriesLength > digits.length) { + throw new Error('Span must be smaller than string length') + } + if (seriesLength < 0) { + throw new Error('Span must be greater than zero') + } + + if (!/^[0-9]+$/g.test(digits)) { + throw new Error('Digits input must only contain digits') + } + + let largestProduct = 0 + for (let i = 0; i <= digits.length - seriesLength; i++) { + const product = digits + .substring(i, i + seriesLength) + .split('') + .map((digit) => Number(digit)) + .reduce((a, b) => a * b) + if (product > largestProduct) { + largestProduct = product + } + } + return largestProduct +} diff --git a/exercises/practice/largest-series-product/largest-series-product.example.ts b/exercises/practice/largest-series-product/largest-series-product.example.ts deleted file mode 100644 index c8ca4704f..000000000 --- a/exercises/practice/largest-series-product/largest-series-product.example.ts +++ /dev/null @@ -1,52 +0,0 @@ -export default class Series { - private numberString: string - private digits: number[] - - constructor(numberString: string) { - if (numberString.match('[^0-9]')) { - throw new Error('Invalid input.') - } - - this.numberString = numberString - this.digits = this.getDigits() - } - - private getDigits(): number[] { - return [...this.numberString].map((digit) => parseInt(digit, 10)) - } - - public largestProduct(size: number): number { - if (size < 0) { - throw new Error('Invalid input.') - } - - let product, - max = 0 - this.slices(size).forEach((slice) => { - product = slice.reduce((a, b) => a * b, 1) - if (product > max) { - max = product - } - }) - return max - } - - private slices(sliceSize: number): number[][] { - const result = [] - let slice = [] - - if (sliceSize > this.digits.length) { - throw new Error('Slice size is too big.') - } - - for (let i = 0; i < this.digits.length - sliceSize + 1; i++) { - for (let j = 0; j < sliceSize; j++) { - slice.push(this.digits[i + j]) - } - result.push(slice) - slice = [] - } - - return result - } -} diff --git a/exercises/practice/largest-series-product/largest-series-product.test.ts b/exercises/practice/largest-series-product/largest-series-product.test.ts index 66cdbe28c..55395de10 100644 --- a/exercises/practice/largest-series-product/largest-series-product.test.ts +++ b/exercises/practice/largest-series-product/largest-series-product.test.ts @@ -1,69 +1,73 @@ -import Series from './largest-series-product' +import { largestProduct } from './largest-series-product' -describe('Series', () => { - it('can get the largest product of 2', () => { - expect(new Series('0123456789').largestProduct(2)).toBe(72) +describe('Largest Series Product', () => { + test('finds the largest product if span equals length', () => { + expect(largestProduct('29', 2)).toEqual(18) }) - xit('works for a tiny number', () => { - expect(new Series('19').largestProduct(2)).toBe(9) + xtest('can find the largest product of 2 with numbers in order', () => { + expect(largestProduct('0123456789', 2)).toEqual(72) }) - xit('can get the largest product of 3', () => { - expect(new Series('1027839564').largestProduct(3)).toBe(270) + xtest('can find the largest product of 2', () => { + expect(largestProduct('576802143', 2)).toEqual(48) }) - xit('can get the largest product of a big number', () => { - const largeNumber = - '73167176531330624919225119674426574742355349194934969835203127745063262395783180169848018694788' + - '51843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648' + - '95044524452316173185640309871112172238311362229893423380308135336276614282806444486645238749303589072962904915604' + - '40772390713810515859307960866701724271218839987979087922749219016997208880937766572733300105336788122023542180975' + - '12545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622' + - '48283972241375657056057490261407972968652414535100474821663704844031998900088952434506585412275886668811642717147' + - '99244429282308634656748139191231628245861786645835912456652947654568284891288314260769004224219022671055626321111' + - '10937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636' + - '899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450' - expect(new Series(largeNumber).largestProduct(13)).toBe(23514624000) + xtest('can find the largest product of 3 with numbers in order', () => { + expect(largestProduct('0123456789', 3)).toEqual(504) }) - xit('returns 0 if all digits are zero', () => { - expect(new Series('0000').largestProduct(2)).toBe(0) + xtest('can find the largest product of 3', () => { + expect(largestProduct('1027839564', 3)).toEqual(270) }) - xit('returns 0 if all spans contain zero', () => { - expect(new Series('99099').largestProduct(3)).toBe(0) + xtest('can find the largest product of 5 with numbers in order', () => { + expect(largestProduct('0123456789', 5)).toEqual(15120) }) - xit('rejects invalid character in input', () => { - expect(() => { - new Series('1234a5').largestProduct(2) - }).toThrowError('Invalid input.') + xtest('can get the largest product of a big number', () => { + expect( + largestProduct('73167176531330624919225119674426574742355349194934', 6) + ).toEqual(23520) }) - xit('rejects negative span', () => { - expect(() => { - new Series('12345').largestProduct(-1) - }).toThrowError('Invalid input.') + xtest('reports zero if the only digits are zero', () => { + expect(largestProduct('0000', 2)).toEqual(0) }) - xit('returns 1 for empty string and zero slice length', () => { - expect(new Series('').largestProduct(0)).toBe(1) + xtest('reports zero if all spans include zero', () => { + expect(largestProduct('99099', 3)).toEqual(0) }) - xit('returns 1 for non-empty string and zero slice length', () => { - expect(new Series('123').largestProduct(0)).toBe(1) + xtest('rejects span longer than string length', () => { + expect(() => largestProduct('123', 4)).toThrow( + new Error('Span must be smaller than string length') + ) }) - xit('throws an error for slices bigger than the number', () => { - expect(() => { - new Series('123').largestProduct(4) - }).toThrowError('Slice size is too big.') + xtest('reports 1 for empty string and empty product (0 span)', () => { + expect(largestProduct('', 0)).toEqual(1) }) - xit('throws an error for empty string and non-zero slice length', () => { - expect(() => { - new Series('').largestProduct(1) - }).toThrowError('Slice size is too big.') + xtest('reports 1 for nonempty string and empty product (0 span)', () => { + expect(largestProduct('123', 0)).toEqual(1) + }) + + xtest('rejects empty string and nonzero span', () => { + expect(() => largestProduct('', 1)).toThrow( + new Error('Span must be smaller than string length') + ) + }) + + xtest('rejects invalid character in digits', () => { + expect(() => largestProduct('1234a5', 2)).toThrow( + new Error('Digits input must only contain digits') + ) + }) + + xtest('rejects negative span', () => { + expect(() => largestProduct('12345', -1)).toThrow( + new Error('Span must be greater than zero') + ) }) }) diff --git a/exercises/practice/largest-series-product/largest-series-product.ts b/exercises/practice/largest-series-product/largest-series-product.ts index e69de29bb..ae65aded4 100644 --- a/exercises/practice/largest-series-product/largest-series-product.ts +++ b/exercises/practice/largest-series-product/largest-series-product.ts @@ -0,0 +1,3 @@ +export const largestProduct = () => { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/leap/.meta/proof.ci.ts b/exercises/practice/leap/.meta/proof.ci.ts new file mode 100644 index 000000000..edb55157d --- /dev/null +++ b/exercises/practice/leap/.meta/proof.ci.ts @@ -0,0 +1,3 @@ +export function isLeapYear(year: number): boolean { + return (year % 100 !== 0 && year % 4 === 0) || year % 400 === 0 +} diff --git a/exercises/practice/leap/leap.example.ts b/exercises/practice/leap/leap.example.ts deleted file mode 100644 index 2a6bb9f8c..000000000 --- a/exercises/practice/leap/leap.example.ts +++ /dev/null @@ -1,5 +0,0 @@ -function isLeapYear(year: number): boolean { - return (year % 100 !== 0 && year % 4 === 0) || year % 400 === 0 -} - -export default isLeapYear diff --git a/exercises/practice/leap/leap.test.ts b/exercises/practice/leap/leap.test.ts index 97e1db80f..19acf7836 100644 --- a/exercises/practice/leap/leap.test.ts +++ b/exercises/practice/leap/leap.test.ts @@ -1,37 +1,39 @@ -import isLeapYear from './leap' +import { isLeap } from './leap' describe('A leap year', () => { - it('is not very common', () => { - expect(isLeapYear(2015)).toBeFalsy() + test('year not divisible by 4 in common year', () => { + expect(isLeap(2015)).toBe(false) }) - xit('is introduced every 4 years to adjust about a day', () => { - expect(isLeapYear(2016)).toBeTruthy() + xtest('year divisible by 2, not divisible by 4 in common year', () => { + expect(isLeap(1970)).toBe(false) }) - xit('is skipped every 100 years to remove an extra day', () => { - expect(isLeapYear(1900)).toBeFalsy() + xtest('year divisible by 4, not divisible by 100 in leap year', () => { + expect(isLeap(1996)).toBe(true) }) - xit('is reintroduced every 400 years to adjust another day', () => { - expect(isLeapYear(2000)).toBeTruthy() + xtest('year divisible by 4 and 5 is still a leap year', () => { + expect(isLeap(1960)).toBe(true) }) - describe('Additional example of a leap year that', () => { - xit('is not a leap year', () => { - expect(isLeapYear(1978)).toBeFalsy() - }) + xtest('year divisible by 100, not divisible by 400 in common year', () => { + expect(isLeap(2100)).toBe(false) + }) - xit('is a common leap year', () => { - expect(isLeapYear(1992)).toBeTruthy() - }) + xtest('year divisible by 100 but not by 3 is still not a leap year', () => { + expect(isLeap(1900)).toBe(false) + }) - xit('is skipped every 100 years', () => { - expect(isLeapYear(2100)).toBeFalsy() - }) + xtest('year divisible by 400 in leap year', () => { + expect(isLeap(2000)).toBe(true) + }) + + xtest('year divisible by 400 but not by 125 is still a leap year', () => { + expect(isLeap(2400)).toBe(true) + }) - xit('is reintroduced every 400 years', () => { - expect(isLeapYear(2400)).toBeTruthy() - }) + xtest('year divisible by 200, not divisible by 400 in common year', () => { + expect(isLeap(1800)).toBe(false) }) }) diff --git a/exercises/practice/leap/leap.ts b/exercises/practice/leap/leap.ts index 5861354ad..1be3601ef 100644 --- a/exercises/practice/leap/leap.ts +++ b/exercises/practice/leap/leap.ts @@ -1,5 +1,3 @@ -function isLeapYear(/* Parameters go here */) { - // Your code here +export function isLeap() { + throw new Error('Remove this statement and implement this function') } - -export default isLeapYear diff --git a/exercises/practice/linked-list/linked-list.example.ts b/exercises/practice/linked-list/.meta/proof.ci.ts similarity index 98% rename from exercises/practice/linked-list/linked-list.example.ts rename to exercises/practice/linked-list/.meta/proof.ci.ts index d30aaa5d0..91b19a84c 100644 --- a/exercises/practice/linked-list/linked-list.example.ts +++ b/exercises/practice/linked-list/.meta/proof.ci.ts @@ -28,7 +28,7 @@ class Node { } } -export default class LinkedList { +export class LinkedList { private head: Node | undefined private tail: Node | undefined diff --git a/exercises/practice/linked-list/linked-list.test.ts b/exercises/practice/linked-list/linked-list.test.ts index baca16c52..c6b5d3da6 100644 --- a/exercises/practice/linked-list/linked-list.test.ts +++ b/exercises/practice/linked-list/linked-list.test.ts @@ -1,4 +1,4 @@ -import LinkedList from './linked-list' +import { LinkedList } from './linked-list' describe('LinkedList', () => { it('add/extract elements to the end of the list with push/pop', () => { diff --git a/exercises/practice/linked-list/linked-list.ts b/exercises/practice/linked-list/linked-list.ts index e69de29bb..e45b4a2d0 100644 --- a/exercises/practice/linked-list/linked-list.ts +++ b/exercises/practice/linked-list/linked-list.ts @@ -0,0 +1,25 @@ +export class LinkedList { + public push(element: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public pop(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public shift(): unknown { + throw new Error('Remove this statement and implement this function') + } + + public unshift(element: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public delete(element: unknown) { + throw new Error('Remove this statement and implement this function') + } + + public count(): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/list-ops/.meta/proof.ci.ts b/exercises/practice/list-ops/.meta/proof.ci.ts new file mode 100644 index 000000000..40b164fd5 --- /dev/null +++ b/exercises/practice/list-ops/.meta/proof.ci.ts @@ -0,0 +1,122 @@ +const Null: Cons = { + get value() { + return undefined + }, + get next() { + return this + }, + get values() { + return [] + }, + + get() { + return this.value + }, + + push(item): Cons { + return new Cons(item, this) + }, + length() { + return 0 + }, + append(other: Cons): Cons { + return other + }, + concat(): Cons { + return this + }, + forEach() { + /* done */ + }, + foldl(_, initial): any { + return initial + }, + foldr(_, initial) { + return initial + }, + filter() { + return Null + }, + reverse(): Cons { + return this + }, + map(): Cons { + return this + }, +} + +class Cons { + static fromArray([head, ...tail]: any[]) { + if (head === undefined) { + return Null + } + + return new Cons(head, Cons.fromArray(tail || [])) + } + + constructor(public readonly value: any, public next: Cons = Null) {} + + get values() { + return [this.value, ...this.next.values] + } + + get(i: number) { + return i === 0 ? this.value : this.next.get(i - 1) + } + + push(item: any): this { + this.next = this.next.push(item) + return this + } + + length(): number { + return 1 + this.next.length() + } + + append(other: Cons): Cons { + return other.foldl((result, item) => result.push(item), this) + } + + concat(others: Cons): Cons { + return others.foldl((result, other) => result.append(other), this) + } + + foldl( + callback: (initial: any, value: any) => any, + initial: any = undefined + ): any { + return this.next.foldl(callback, callback(initial, this.value)) + } + + forEach(callback: (value: any) => void): void { + this.foldl((_, item) => callback(item)) + } + + foldr( + callback: (initial: any, value: any) => any, + initial: any = undefined + ): any { + return callback(this.next.foldr(callback, initial), this.value) + } + + filter(predicate: (value: any) => boolean): Cons { + return this.foldl( + (result, item) => (predicate(item) && result.push(item)) || result, + Null + ) + } + + map(expression: (value: any) => any): Cons { + return this.foldl((result, item) => result.push(expression(item)), Null) + } + + reverse(): Cons { + return this.next.reverse().push(this.value) + } +} + +export class List { + constructor(values = []) { + return Cons.fromArray(values) + } +} diff --git a/exercises/practice/list-ops/list-ops.example.ts b/exercises/practice/list-ops/list-ops.example.ts deleted file mode 100644 index 6d6cb3302..000000000 --- a/exercises/practice/list-ops/list-ops.example.ts +++ /dev/null @@ -1,78 +0,0 @@ -class List { - public values: T[] - - constructor(arr?: T[]) { - this.values = arr || [] - } - - public append(otherList: List): this { - for (const el of otherList.values) { - this.values.push(el) - } - return this - } - - public concat(listOfLists: List>): this { - for (const list of listOfLists.values) { - this.append(list) - } - return this - } - - public filter(operation: (arg: T) => boolean): this { - const filteredValues = [] - for (const el of this.values) { - if (operation(el)) { - filteredValues.push(el) - } - } - this.values = filteredValues - return this - } - - public length(): number { - let length = 0 - length += this.values.length - return length - } - - public map(operation: (arg: T) => T): this { - const mappedValues = [] - for (const el of this.values) { - mappedValues.push(operation(el)) - } - this.values = mappedValues - return this - } - - public foldl(operation: (arg1: A, arg2: T) => A, initialValue: A): A { - let acc = initialValue - for (const el of this.values) { - acc = operation(acc, el) - } - return acc - } - - public foldr(operation: (arg1: A, arg2: T) => A, initialValue: A): A { - let acc = initialValue - let index = this.length() - 1 - while (index >= 0) { - const el = this.values[index--] - acc = operation(acc, el) - } - return acc - } - - public reverse(): this { - const numElements = this.length() - let finalIndex = numElements - 1 - for (let index = 0; index < numElements / 2; index++) { - const temp = this.values[index] - this.values[index] = this.values[finalIndex] - this.values[finalIndex--] = temp - } - return this - } -} - -export default List diff --git a/exercises/practice/list-ops/list-ops.test.ts b/exercises/practice/list-ops/list-ops.test.ts index c16c63e82..63ac3ac66 100644 --- a/exercises/practice/list-ops/list-ops.test.ts +++ b/exercises/practice/list-ops/list-ops.test.ts @@ -1,19 +1,19 @@ -import List from './list-ops' +import { List } from './list-ops' describe('append entries to a list and return the new list', () => { - it('empty lists', () => { + test('empty lists', () => { const list1 = new List() const list2 = new List() expect(list1.append(list2)).toEqual(new List()) }) - xit('empty list to list', () => { + xtest('empty list to list', () => { const list1 = new List([1, 2, 3, 4]) - const list2 = new List() + const list2 = new List() expect(list1.append(list2)).toEqual(list1) }) - xit('non-empty lists', () => { + xtest('non-empty lists', () => { const list1 = new List([1, 2]) const list2 = new List([2, 3, 4, 5]) expect(list1.append(list2).values).toEqual([1, 2, 2, 3, 4, 5]) @@ -21,13 +21,13 @@ describe('append entries to a list and return the new list', () => { }) describe('concat lists and lists of lists into new list', () => { - xit('empty list', () => { + xtest('empty list', () => { const list1 = new List() - const list2 = new List([]) + const list2 = new List() expect(list1.concat(list2).values).toEqual([]) }) - xit('list of lists', () => { + xtest('list of lists', () => { const list1 = new List([1, 2]) const list2 = new List([3]) const list3 = new List([]) @@ -38,73 +38,88 @@ describe('concat lists and lists of lists into new list', () => { }) describe('filter list returning only values that satisfy the filter function', () => { - xit('empty list', () => { + xtest('empty list', () => { const list1 = new List([]) - expect(list1.filter((el: number) => el % 2 === 1).values).toEqual([]) + expect(list1.filter((el) => el % 2 === 1).values).toEqual([]) }) - xit('non empty list', () => { + xtest('non empty list', () => { const list1 = new List([1, 2, 3, 5]) - expect(list1.filter((el: number) => el % 2 === 1).values).toEqual([1, 3, 5]) + expect(list1.filter((el) => el % 2 === 1).values).toEqual([1, 3, 5]) }) }) describe('returns the length of a list', () => { - xit('empty list', () => { + xtest('empty list', () => { const list1 = new List() expect(list1.length()).toEqual(0) }) - xit('non-empty list', () => { + xtest('non-empty list', () => { const list1 = new List([1, 2, 3, 4]) expect(list1.length()).toEqual(4) }) }) describe('returns a list of elements whose values equal the list value transformed by the mapping function', () => { - xit('empty list', () => { - const list1 = new List() - expect(list1.map((el: number) => ++el).values).toEqual([]) + xtest('empty list', () => { + const list1 = new List() + expect(list1.map((el) => ++el).values).toEqual([]) }) - xit('non-empty list', () => { + xtest('non-empty list', () => { const list1 = new List([1, 3, 5, 7]) - expect(list1.map((el: number) => ++el).values).toEqual([2, 4, 6, 8]) + expect(list1.map((el) => ++el).values).toEqual([2, 4, 6, 8]) }) }) describe('folds (reduces) the given list from the left with a function', () => { - xit('empty list', () => { - const list1 = new List() - expect(list1.foldl((acc: number, el: number) => el / acc, 2)).toEqual(2) + xtest('empty list', () => { + const list1 = new List() + expect(list1.foldl((acc, el) => el * acc, 2)).toEqual(2) + }) + + xtest('direction independent function applied to non-empty list', () => { + const list1 = new List([1, 2, 3, 4]) + expect(list1.foldl((acc, el) => acc + el, 5)).toEqual(15) }) - xit('division of integers', () => { + xtest('direction dependent function applied to non-empty list', () => { const list1 = new List([1, 2, 3, 4]) - expect(list1.foldl((acc: number, el: number) => el / acc, 24)).toEqual(64) + expect(list1.foldl((acc, el) => el / acc, 24)).toEqual(64) }) }) describe('folds (reduces) the given list from the right with a function', () => { - xit('empty list', () => { - const list1 = new List() - expect(list1.foldr((acc: number, el: number) => el / acc, 2)).toEqual(2) + xtest('empty list', () => { + const list1 = new List() + expect(list1.foldr((acc, el) => el * acc, 2)).toEqual(2) + }) + + xtest('direction independent function applied to non-empty list', () => { + const list1 = new List([1, 2, 3, 4]) + expect(list1.foldr((acc, el) => acc + el, 5)).toEqual(15) }) - xit('division of integers', () => { + xtest('direction dependent function applied to non-empty list', () => { const list1 = new List([1, 2, 3, 4]) - expect(list1.foldr((acc: number, el: number) => el / acc, 24)).toEqual(9) + expect(list1.foldr((acc, el) => el / acc, 24)).toEqual(9) }) }) describe('reverse the elements of a list', () => { - xit('empty list', () => { + xtest('empty list', () => { const list1 = new List() expect(list1.reverse().values).toEqual([]) }) - xit('non-empty list', () => { + xtest('non-empty list', () => { const list1 = new List([1, 3, 5, 7]) expect(list1.reverse().values).toEqual([7, 5, 3, 1]) }) + + xtest('list of lists is not flattened', () => { + const list1 = new List([[1, 2], [3], [], [4, 5, 6]]) + expect(list1.reverse().values).toEqual([[4, 5, 6], [], [3], [1, 2]]) + }) }) diff --git a/exercises/practice/list-ops/list-ops.ts b/exercises/practice/list-ops/list-ops.ts index e69de29bb..ccdc4f13d 100644 --- a/exercises/practice/list-ops/list-ops.ts +++ b/exercises/practice/list-ops/list-ops.ts @@ -0,0 +1,37 @@ +export class List { + constructor() { + throw new Error('Remove this statement and implement this function') + } + + append() { + throw new Error('Remove this statement and implement this function') + } + + concat() { + throw new Error('Remove this statement and implement this function') + } + + filter() { + throw new Error('Remove this statement and implement this function') + } + + map() { + throw new Error('Remove this statement and implement this function') + } + + length() { + throw new Error('Remove this statement and implement this function') + } + + foldl() { + throw new Error('Remove this statement and implement this function') + } + + foldr() { + throw new Error('Remove this statement and implement this function') + } + + reverse() { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/luhn/.meta/proof.ci.ts b/exercises/practice/luhn/.meta/proof.ci.ts new file mode 100644 index 000000000..f9aa88c66 --- /dev/null +++ b/exercises/practice/luhn/.meta/proof.ci.ts @@ -0,0 +1,26 @@ +export function valid(value: string): boolean { + const numbers = value.replace(/\s/g, '') + const digits = [...numbers] + + const sum = digits + // convert to integers + .map((d) => parseInt(d, 10)) + // double even positions (odd indexes) + .map((d, i) => { + if (i % 2 !== 0) { + return d * 2 + } + return d + }) + // limit to digits less than 10 + .map((d) => { + if (d > 9) { + return d - 9 + } + return d + }) + // sum all digits + .reduce((d, acc) => d + acc, 0) + + return sum > 0 && sum % 10 === 0 +} diff --git a/exercises/practice/luhn/luhn.example.ts b/exercises/practice/luhn/luhn.example.ts deleted file mode 100644 index 3f1a85644..000000000 --- a/exercises/practice/luhn/luhn.example.ts +++ /dev/null @@ -1,30 +0,0 @@ -class Luhn { - public static valid(value: string): boolean { - const numbers = value.replace(/\s/g, '') - const digits = [...numbers] - - const sum = digits - // convert to integers - .map((d) => parseInt(d, 10)) - // double even positions (odd indexes) - .map((d, i) => { - if (i % 2 !== 0) { - return d * 2 - } - return d - }) - // limit to digits less than 10 - .map((d) => { - if (d > 9) { - return d - 9 - } - return d - }) - // sum all digits - .reduce((d, acc) => d + acc, 0) - - return sum > 0 && sum % 10 === 0 - } -} - -export default Luhn diff --git a/exercises/practice/luhn/luhn.test.ts b/exercises/practice/luhn/luhn.test.ts index e536048fc..665b7bc60 100644 --- a/exercises/practice/luhn/luhn.test.ts +++ b/exercises/practice/luhn/luhn.test.ts @@ -1,47 +1,47 @@ -import Luhn from './luhn' +import { valid } from './luhn' describe('Luhn', () => { it('single digit strings can not be valid', () => { - expect(Luhn.valid('1')).toBeFalsy() + expect(valid('1')).toBeFalsy() }) xit('a single zero is invalid', () => { - expect(Luhn.valid('0')).toBeFalsy() + expect(valid('0')).toBeFalsy() }) xit('a simple valid SIN that remains valid if reversed', () => { - expect(Luhn.valid('059')).toBeTruthy() + expect(valid('059')).toBeTruthy() }) xit('a valid Canadian SIN', () => { - expect(Luhn.valid('055 444 285')).toBeTruthy() + expect(valid('055 444 285')).toBeTruthy() }) xit('invalid Canadian SIN', () => { - expect(Luhn.valid('055 444 286')).toBeFalsy() + expect(valid('055 444 286')).toBeFalsy() }) xit('invalid credit card', () => { - expect(Luhn.valid('8273 1232 7352 0569')).toBeFalsy() + expect(valid('8273 1232 7352 0569')).toBeFalsy() }) xit('valid strings with a non-digit included become invalid', () => { - expect(Luhn.valid('055a 444 285')).toBeFalsy() + expect(valid('055a 444 285')).toBeFalsy() }) xit('valid strings with punctuation included become invalid', () => { - expect(Luhn.valid('055-444-285')).toBeFalsy() + expect(valid('055-444-285')).toBeFalsy() }) xit('valid strings with symbols included become invalid', () => { - expect(Luhn.valid('055£ 444$ 285')).toBeFalsy() + expect(valid('055£ 444$ 285')).toBeFalsy() }) xit('single zero with space is invalid', () => { - expect(Luhn.valid(' 0')).toBeFalsy() + expect(valid(' 0')).toBeFalsy() }) xit('input digit 9 is correctly converted to output digit 9', () => { - expect(Luhn.valid('091')).toBeTruthy() + expect(valid('091')).toBeTruthy() }) }) diff --git a/exercises/practice/luhn/luhn.ts b/exercises/practice/luhn/luhn.ts index e69de29bb..ab2b42fc1 100644 --- a/exercises/practice/luhn/luhn.ts +++ b/exercises/practice/luhn/luhn.ts @@ -0,0 +1,3 @@ +export function valid(digitString: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/matching-brackets/.meta/proof.ci.ts b/exercises/practice/matching-brackets/.meta/proof.ci.ts new file mode 100644 index 000000000..c8dbbf3e0 --- /dev/null +++ b/exercises/practice/matching-brackets/.meta/proof.ci.ts @@ -0,0 +1,22 @@ +export function isPaired(input: string): boolean { + const brackets = input.replace(/[^{(\[\])}]/g, '') + const bracketsAreMatching = (leftBracket, rightBracket) => + (leftBracket === '(' && rightBracket === ')') || + (leftBracket === '[' && rightBracket === ']') || + (leftBracket === '{' && rightBracket === '}') + + let arr = [] + for (let letter of brackets) { + if (arr.length >= 1) { + const lastBracket = arr[arr.length - 1] + if (bracketsAreMatching(lastBracket, letter)) { + arr.pop() + } else { + arr.push(letter) + } + } else { + arr.push(letter) + } + } + return arr.length === 0 +} diff --git a/exercises/practice/matching-brackets/matching-brackets.example.ts b/exercises/practice/matching-brackets/matching-brackets.example.ts deleted file mode 100644 index caf05cae2..000000000 --- a/exercises/practice/matching-brackets/matching-brackets.example.ts +++ /dev/null @@ -1,50 +0,0 @@ -class MatchingBrackets { - private bracketPairs: Map - private readonly expression: string - - constructor(expression: string) { - this.bracketPairs = new Map() - this.bracketPairs.set('{', '}') - this.bracketPairs.set('[', ']') - this.bracketPairs.set('(', ')') - - this.expression = expression - } - - public isPaired(): boolean { - const bracketStack: string[] = [] - const expressionSplitted = this.expression.split('') - - for (let i = 0; i <= expressionSplitted.length - 1; i++) { - const element = expressionSplitted[i] - - if (this.isOpeningBracket(element)) { - bracketStack.push(element) - } else if (this.isClosingBracket(element)) { - if (bracketStack === []) { - return false - } - - if (this.bracketPairs.get(String(bracketStack.pop())) !== element) { - return false - } - } - } - - return bracketStack.length === 0 - } - - private isOpeningBracket(bracket: string): boolean { - return this.bracketPairs.has(bracket) - } - - private isClosingBracket(bracket: string): boolean { - if (bracket === '}' || bracket === ']' || bracket === ')') { - return true - } else { - return false - } - } -} - -export default MatchingBrackets diff --git a/exercises/practice/matching-brackets/matching-brackets.test.ts b/exercises/practice/matching-brackets/matching-brackets.test.ts index 80e3b7005..79badc31e 100644 --- a/exercises/practice/matching-brackets/matching-brackets.test.ts +++ b/exercises/practice/matching-brackets/matching-brackets.test.ts @@ -1,77 +1,75 @@ -import MatchingBrackets from './matching-brackets' +import { isPaired } from './matching-brackets' describe('Matching Brackets', () => { - it('paired square brackets', () => { - const matchingBrackets = new MatchingBrackets('[]') - expect(matchingBrackets.isPaired()).toBeTruthy() + test('paired square brackets', () => { + expect(isPaired('[]')).toEqual(true) }) - xit('empty string', () => { - const matchingBrackets = new MatchingBrackets('') - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('empty string', () => { + expect(isPaired('')).toEqual(true) }) - xit('unpaired brackets', () => { - const matchingBrackets = new MatchingBrackets('[[') - expect(matchingBrackets.isPaired()).toBeFalsy() + xtest('unpaired brackets', () => { + expect(isPaired('[[')).toEqual(false) }) - xit('wrong ordered brackets', () => { - const matchingBrackets = new MatchingBrackets('}{') - expect(matchingBrackets.isPaired()).toBeFalsy() + xtest('wrong ordered brackets', () => { + expect(isPaired('}{')).toEqual(false) }) - xit('wrong closing bracket', () => { - const matchingBrackets = new MatchingBrackets('{]') - expect(matchingBrackets.isPaired()).toBeFalsy() + xtest('wrong closing bracket', () => { + expect(isPaired('{]')).toEqual(false) }) - xit('paired with whitespace', () => { - const matchingBrackets = new MatchingBrackets('{ }') - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('paired with whitespace', () => { + expect(isPaired('{ }')).toEqual(true) }) - xit('simple nested brackets', () => { - const matchingBrackets = new MatchingBrackets('{[]}') - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('partially paired brackets', () => { + expect(isPaired('{[])')).toEqual(false) }) - xit('several paired brackets', () => { - const matchingBrackets = new MatchingBrackets('{}[]') - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('simple nested brackets', () => { + expect(isPaired('{[]}')).toEqual(true) }) - xit('paired and nested brackets', () => { - const matchingBrackets = new MatchingBrackets('([{}({}[])])') - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('several paired brackets', () => { + expect(isPaired('{}[]')).toEqual(true) }) - xit('unopened closing brackets', () => { - const matchingBrackets = new MatchingBrackets('{[)][]}') - expect(matchingBrackets.isPaired()).toBeFalsy() + xtest('paired and nested brackets', () => { + expect(isPaired('([{}({}[])])')).toEqual(true) }) - xit('unpaired and nested brackets', () => { - const matchingBrackets = new MatchingBrackets('([{])') - expect(matchingBrackets.isPaired()).toBeFalsy() + xtest('unopened closing brackets', () => { + expect(isPaired('{[)][]}')).toEqual(false) }) - xit('paired and wrong nested brackets', () => { - const matchingBrackets = new MatchingBrackets('[({]})') - expect(matchingBrackets.isPaired()).toBeFalsy() + xtest('unpaired and nested brackets', () => { + expect(isPaired('([{])')).toEqual(false) }) - xit('math expression', () => { - const matchingBrackets = new MatchingBrackets( - '(((185 + 223.85) * 15) - 543)/2' - ) - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('paired and wrong nested brackets', () => { + expect(isPaired('[({]})')).toEqual(false) }) - xit('complex latex expression', () => { - const matchingBrackets = new MatchingBrackets( - '\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)' - ) - expect(matchingBrackets.isPaired()).toBeTruthy() + xtest('paired and incomplete brackets', () => { + expect(isPaired('{}[')).toEqual(false) + }) + + xtest('too many closing brackets', () => { + expect(isPaired('[]]')).toEqual(false) + }) + + xtest('math expression', () => { + expect(isPaired('(((185 + 223.85) * 15) - 543)/2')).toEqual(true) + }) + + xtest('complex latex expression', () => { + expect( + isPaired( + '\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)' + ) + ).toEqual(true) }) }) diff --git a/exercises/practice/matching-brackets/matching-brackets.ts b/exercises/practice/matching-brackets/matching-brackets.ts index e69de29bb..33087b9e4 100644 --- a/exercises/practice/matching-brackets/matching-brackets.ts +++ b/exercises/practice/matching-brackets/matching-brackets.ts @@ -0,0 +1,3 @@ +export function isPaired(input: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/matrix/matrix.example.ts b/exercises/practice/matrix/.meta/proof.ci.ts similarity index 95% rename from exercises/practice/matrix/matrix.example.ts rename to exercises/practice/matrix/.meta/proof.ci.ts index 18535730d..c6afa4aac 100644 --- a/exercises/practice/matrix/matrix.example.ts +++ b/exercises/practice/matrix/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -class Matrix { +export class Matrix { private description: string public rows: number[][] = new Array() public columns: number[][] = new Array() @@ -32,5 +32,3 @@ class Matrix { } } } - -export default Matrix diff --git a/exercises/practice/matrix/matrix.test.ts b/exercises/practice/matrix/matrix.test.ts index b4272a8b5..2c2082346 100644 --- a/exercises/practice/matrix/matrix.test.ts +++ b/exercises/practice/matrix/matrix.test.ts @@ -1,4 +1,4 @@ -import Matrix from './matrix' +import { Matrix } from './matrix' describe('Matrix', () => { it('extract row from one number matrix', () => { diff --git a/exercises/practice/matrix/matrix.ts b/exercises/practice/matrix/matrix.ts index 8aa8eab47..b24da9d20 100644 --- a/exercises/practice/matrix/matrix.ts +++ b/exercises/practice/matrix/matrix.ts @@ -1,7 +1,13 @@ -class Matrix { - constructor(/* Parameters go here */) { - // Your code here +export class Matrix { + constructor() { + throw new Error('Remove this statement and implement this function') + } + + get rows(): unknown { + throw new Error('Remove this statement and implement this function') } -} -export default Matrix + get columns(): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/minesweeper/minesweeper.example.ts b/exercises/practice/minesweeper/.meta/proof.ci.ts similarity index 73% rename from exercises/practice/minesweeper/minesweeper.example.ts rename to exercises/practice/minesweeper/.meta/proof.ci.ts index 4e2d2437e..9c48870b2 100644 --- a/exercises/practice/minesweeper/minesweeper.example.ts +++ b/exercises/practice/minesweeper/.meta/proof.ci.ts @@ -11,20 +11,18 @@ const DELTAS = [ [0, -1], ] -class Minesweeper { - public annotate(rows: string[]): string[] { - if (noDataPresent(rows)) { - return rows - } +export function annotate(rows: string[]): string[] { + if (noDataPresent(rows)) { + return rows + } - const inputBoard = rows.map((row) => [...row]) + const inputBoard = rows.map((row) => [...row]) - const result = inputBoard.map((row, x) => - [...row].map((cell, y) => cellToMineOrCount(cell, inputBoard, x, y)) - ) + const result = inputBoard.map((row, x) => + [...row].map((cell, y) => cellToMineOrCount(cell, inputBoard, x, y)) + ) - return stringify(result as string[][]) - } + return stringify(result as string[][]) } function cellToMineOrCount( @@ -69,5 +67,3 @@ function adjacentSquareHasMine( ): boolean { return board[x + d[0]][y + d[1]] === MINE } - -export default Minesweeper diff --git a/exercises/practice/minesweeper/minesweeper.test.ts b/exercises/practice/minesweeper/minesweeper.test.ts index 270679f8a..db6f1391b 100644 --- a/exercises/practice/minesweeper/minesweeper.test.ts +++ b/exercises/practice/minesweeper/minesweeper.test.ts @@ -1,72 +1,66 @@ -import Minesweeper from './minesweeper' - -describe('Minesweeper()', () => { - let minesweeper: Minesweeper - - beforeEach(() => { - minesweeper = new Minesweeper() - }) +import { annotate } from './minesweeper' +describe('Minesweeper annotate', () => { it('handles no rows', () => { - expect(minesweeper.annotate([])).toEqual([]) + expect(annotate([])).toEqual([]) }) xit('handles no columns', () => { - expect(minesweeper.annotate([''])).toEqual(['']) + expect(annotate([''])).toEqual(['']) }) xit('handles no mines', () => { const input = [' ', ' ', ' '] const expected = [' ', ' ', ' '] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles board with only mines', () => { const input = ['***', '***', '***'] const expected = ['***', '***', '***'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles mine surrounded by spaces', () => { const input = [' ', ' * ', ' '] const expected = ['111', '1*1', '111'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles space surrounded by mines', () => { const input = ['***', '* *', '***'] const expected = ['***', '*8*', '***'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles horizontal line', () => { const input = [' * * '] const expected = ['1*2*1'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles horizontal line, mines at edges', () => { const input = ['* *'] const expected = ['*1 1*'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles vertical line', () => { const input = [' ', '*', ' ', '*', ' '] const expected = ['1', '*', '2', '*', '1'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles vertical line, mines at edges', () => { const input = ['*', ' ', ' ', ' ', '*'] const expected = ['*', '1', ' ', '1', '*'] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles cross', () => { const input = [' * ', ' * ', '*****', ' * ', ' * '] const expected = [' 2*2 ', '25*52', '*****', '25*52', ' 2*2 '] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) xit('handles large board', () => { @@ -79,6 +73,6 @@ describe('Minesweeper()', () => { '1*22*2', '111111', ] - expect(minesweeper.annotate(input)).toEqual(expected) + expect(annotate(input)).toEqual(expected) }) }) diff --git a/exercises/practice/minesweeper/minesweeper.ts b/exercises/practice/minesweeper/minesweeper.ts index e69de29bb..a8b6607bb 100644 --- a/exercises/practice/minesweeper/minesweeper.ts +++ b/exercises/practice/minesweeper/minesweeper.ts @@ -0,0 +1,3 @@ +export function annotate(field: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/nth-prime/nth-prime.example.ts b/exercises/practice/nth-prime/.meta/nth-prime.example.ts similarity index 60% rename from exercises/practice/nth-prime/nth-prime.example.ts rename to exercises/practice/nth-prime/.meta/nth-prime.example.ts index cbf9f2d53..889dc5e26 100644 --- a/exercises/practice/nth-prime/nth-prime.example.ts +++ b/exercises/practice/nth-prime/.meta/nth-prime.example.ts @@ -15,14 +15,10 @@ const range = (min: number, max: number): number[] => { return result } -class Prime { - public nth(nthPrime: number): number { - if (nthPrime === 0) { - throw new Error('Prime is not possible') - } - - return range(2, 2e6).filter(isPrime)[nthPrime - 1] +export function nth(nthPrime: number): number { + if (nthPrime === 0) { + throw new Error('Prime is not possible') } -} -export default Prime + return range(2, 2e6).filter(isPrime)[nthPrime - 1] +} diff --git a/exercises/practice/nth-prime/nth-prime.test.ts b/exercises/practice/nth-prime/nth-prime.test.ts index 91f3cdc9b..c84a9d01b 100644 --- a/exercises/practice/nth-prime/nth-prime.test.ts +++ b/exercises/practice/nth-prime/nth-prime.test.ts @@ -1,25 +1,23 @@ -import Prime from './nth-prime' +import { nth } from './nth-prime' describe('Prime', () => { - const prime = new Prime() - it('first', () => { - expect(prime.nth(1)).toEqual(2) + expect(nth(1)).toEqual(2) }) xit('second', () => { - expect(prime.nth(2)).toEqual(3) + expect(nth(2)).toEqual(3) }) xit('sixth', () => { - expect(prime.nth(6)).toEqual(13) + expect(nth(6)).toEqual(13) }) xit('big prime', () => { - expect(prime.nth(10001)).toEqual(104743) + expect(nth(10001)).toEqual(104743) }) xit('weird case', () => { - expect(() => prime.nth(0)).toThrowError('Prime is not possible') + expect(() => nth(0)).toThrowError('Prime is not possible') }) }) diff --git a/exercises/practice/nth-prime/nth-prime.ts b/exercises/practice/nth-prime/nth-prime.ts index e69de29bb..b12e53837 100644 --- a/exercises/practice/nth-prime/nth-prime.ts +++ b/exercises/practice/nth-prime/nth-prime.ts @@ -0,0 +1,3 @@ +export function nth() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/nucleotide-count/.meta/proof.ci.ts b/exercises/practice/nucleotide-count/.meta/proof.ci.ts new file mode 100644 index 000000000..5add6bbe9 --- /dev/null +++ b/exercises/practice/nucleotide-count/.meta/proof.ci.ts @@ -0,0 +1,31 @@ +type Counts = Record<'A' | 'C' | 'G' | 'T', number> + +export function nucleotideCounts(strand: string): Counts { + const nucleotideOccurrences: Counts = { + A: 0, + C: 0, + G: 0, + T: 0, + } + + strand.split('').forEach((nucleotide) => { + if (nucleotide in nucleotideOccurrences) { + if (nucleotide === 'A') { + nucleotideOccurrences.A++ + } + if (nucleotide === 'C') { + nucleotideOccurrences.C++ + } + if (nucleotide === 'G') { + nucleotideOccurrences.G++ + } + if (nucleotide === 'T') { + nucleotideOccurrences.T++ + } + } else { + throw new Error('Invalid nucleotide in strand') + } + }) + + return nucleotideOccurrences +} diff --git a/exercises/practice/nucleotide-count/nucleotide-count.example.ts b/exercises/practice/nucleotide-count/nucleotide-count.example.ts deleted file mode 100644 index 38efc115d..000000000 --- a/exercises/practice/nucleotide-count/nucleotide-count.example.ts +++ /dev/null @@ -1,35 +0,0 @@ -type Counts = Record<'A' | 'C' | 'G' | 'T', number> - -class NucleotideCount { - public static nucleotideCounts(strand: string): Counts { - const nucleotideOccurrences: Counts = { - A: 0, - C: 0, - G: 0, - T: 0, - } - - strand.split('').forEach((nucleotide) => { - if (nucleotide in nucleotideOccurrences) { - if (nucleotide === 'A') { - nucleotideOccurrences.A++ - } - if (nucleotide === 'C') { - nucleotideOccurrences.C++ - } - if (nucleotide === 'G') { - nucleotideOccurrences.G++ - } - if (nucleotide === 'T') { - nucleotideOccurrences.T++ - } - } else { - throw new Error('Invalid nucleotide in strand') - } - }) - - return nucleotideOccurrences - } -} - -export default NucleotideCount diff --git a/exercises/practice/nucleotide-count/nucleotide-count.test.ts b/exercises/practice/nucleotide-count/nucleotide-count.test.ts index 4316a9400..f659d82f1 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count.test.ts +++ b/exercises/practice/nucleotide-count/nucleotide-count.test.ts @@ -1,4 +1,4 @@ -import NucleotideCount from './nucleotide-count' +import { nucleotideCounts } from './nucleotide-count' describe('count all nucleotides in a strand', () => { it('empty strand', () => { @@ -8,7 +8,7 @@ describe('count all nucleotides in a strand', () => { G: 0, T: 0, } - expect(NucleotideCount.nucleotideCounts('')).toEqual(expected) + expect(nucleotideCounts('')).toEqual(expected) }) xit('can count one nucleotide in single-character input', () => { @@ -18,7 +18,7 @@ describe('count all nucleotides in a strand', () => { G: 1, T: 0, } - expect(NucleotideCount.nucleotideCounts('G')).toEqual(expected) + expect(nucleotideCounts('G')).toEqual(expected) }) xit('strand with repeated nucleotide', () => { @@ -28,7 +28,7 @@ describe('count all nucleotides in a strand', () => { G: 7, T: 0, } - expect(NucleotideCount.nucleotideCounts('GGGGGGG')).toEqual(expected) + expect(nucleotideCounts('GGGGGGG')).toEqual(expected) }) xit('strand with multiple nucleotides', () => { @@ -39,7 +39,7 @@ describe('count all nucleotides in a strand', () => { T: 21, } expect( - NucleotideCount.nucleotideCounts( + nucleotideCounts( 'AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC' ) ).toEqual(expected) @@ -48,7 +48,7 @@ describe('count all nucleotides in a strand', () => { xit('strand with invalid nucleotides', () => { const expected = 'Invalid nucleotide in strand' expect(() => { - NucleotideCount.nucleotideCounts('AGXXACT') + nucleotideCounts('AGXXACT') }).toThrowError(expected) }) }) diff --git a/exercises/practice/nucleotide-count/nucleotide-count.ts b/exercises/practice/nucleotide-count/nucleotide-count.ts index aaf547b16..101a2b05f 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count.ts +++ b/exercises/practice/nucleotide-count/nucleotide-count.ts @@ -1,7 +1,3 @@ -class NucleotideCount { - static nucleotideCounts(/* Parameters go here */) { - // Your code here - } +export function nucleotideCounts(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') } - -export default NucleotideCount diff --git a/exercises/practice/ocr-numbers/.meta/proof.ci.ts b/exercises/practice/ocr-numbers/.meta/proof.ci.ts new file mode 100644 index 000000000..874a0f736 --- /dev/null +++ b/exercises/practice/ocr-numbers/.meta/proof.ci.ts @@ -0,0 +1,60 @@ +interface Patterns { + [key: number]: string[] +} + +const PATTERNS: Patterns = { + 0: [' _ ', '| |', '|_|', ' '], + 1: [' ', ' |', ' |', ' '], + 2: [' _ ', ' _|', '|_ ', ' '], + 3: [' _ ', ' _|', ' _|', ' '], + 4: [' ', '|_|', ' |', ' '], + 5: [' _ ', '|_ ', ' _|', ' '], + 6: [' _ ', '|_ ', '|_|', ' '], + 7: [' _ ', ' |', ' |', ' '], + 8: [' _ ', '|_|', '|_|', ' '], + 9: [' _ ', '|_|', ' _|', ' '], +} + +type Digit = number | '?' + +const splitIntoRows = (text: string): string[] => { + const rows: string[] = [] + const lines = text.split('\n') + for (let rowNumber = 0; rowNumber < lines.length; rowNumber += 4) { + let row = '' + for (let rowLine = 0; rowLine < 4; rowLine += 1) { + row += `${lines[rowNumber + rowLine]}\n` + } + rows.push(row.slice(0, -1)) + } + return rows +} + +const splitIntoDigits = (row: string): string[] => { + const digits: string[] = [] + const rows = row.split('\n') + for (let digitNumber = 0; digitNumber < rows[0].length; digitNumber += 3) { + let digit = '' + for (let rowNumber = 0; rowNumber < rows.length; rowNumber += 1) { + digit += rows[rowNumber].substr(digitNumber, 3) + } + digits.push(digit) + } + return digits +} + +const getDigit = (text: string): Digit => { + const digit = Object.values(PATTERNS) + .map((x) => x.join('')) + .indexOf(text) + if (digit === -1) { + return '?' + } + return digit +} + +const valuesInRow = (row: string): string => + splitIntoDigits(row).map(getDigit).join('') + +export const convert = (text: string): string => + splitIntoRows(text).map(valuesInRow).join(',') diff --git a/exercises/practice/ocr-numbers/ocr-numbers.example.ts b/exercises/practice/ocr-numbers/ocr-numbers.example.ts deleted file mode 100644 index dfb8091ae..000000000 --- a/exercises/practice/ocr-numbers/ocr-numbers.example.ts +++ /dev/null @@ -1,61 +0,0 @@ -interface Patterns { - [key: number]: string[] -} - -const PATTERNS: Patterns = { - 0: [' _ ', '| |', '|_|', ' '], - 1: [' ', ' |', ' |', ' '], - 2: [' _ ', ' _|', '|_ ', ' '], - 3: [' _ ', ' _|', ' _|', ' '], - 4: [' ', '|_|', ' |', ' '], - 5: [' _ ', '|_ ', ' _|', ' '], - 6: [' _ ', '|_ ', '|_|', ' '], - 7: [' _ ', ' |', ' |', ' '], - 8: [' _ ', '|_|', '|_|', ' '], - 9: [' _ ', '|_|', ' _|', ' '], -} - -export default class OcrParser { - public static convert(text: string): string { - return OcrParser.splitIntoRows(text).map(OcrParser.valuesInRow).join(',') - } - - private static valuesInRow(row: string): string { - return OcrParser.splitIntoDigits(row).map(OcrParser.getDigit).join('') - } - - private static splitIntoRows(text: string): string[] { - const rows = [] - const lines = text.split('\n') - for (let rowNumber = 0; rowNumber < lines.length; rowNumber += 4) { - let row = '' - for (let rowLine = 0; rowLine < 4; rowLine++) { - row += `${lines[rowNumber + rowLine]}\n` - } - rows.push(row.slice(0, -1)) - } - return rows - } - - private static splitIntoDigits(row: string): string[] { - const digits = [], - rows = row.split('\n') - for (let digitNumber = 0; digitNumber < rows[0].length; digitNumber += 3) { - let digit = '' - for (const row of rows) { - digit += row.substr(digitNumber, 3) - } - digits.push(digit) - } - return digits - } - - private static getDigit(text: string): string { - for (const digit in PATTERNS) { - if (PATTERNS[digit].join('') === text) { - return digit - } - } - return '?' - } -} diff --git a/exercises/practice/ocr-numbers/ocr-numbers.test.ts b/exercises/practice/ocr-numbers/ocr-numbers.test.ts index 56983913c..59207ee23 100644 --- a/exercises/practice/ocr-numbers/ocr-numbers.test.ts +++ b/exercises/practice/ocr-numbers/ocr-numbers.test.ts @@ -1,59 +1,57 @@ -import OcrParser from './ocr-numbers' +import { convert } from './ocr-numbers' describe('ocr', () => { it('recognizes zero', () => { - expect(OcrParser.convert(' _ \n' + '| |\n' + '|_|\n' + ' ')).toBe('0') + expect(convert(' _ \n' + '| |\n' + '|_|\n' + ' ')).toBe('0') }) xit('recognizes one', () => { - expect(OcrParser.convert(' \n' + ' |\n' + ' |\n' + ' ')).toBe('1') + expect(convert(' \n' + ' |\n' + ' |\n' + ' ')).toBe('1') }) xit('recognizes two', () => { - expect(OcrParser.convert(' _ \n' + ' _|\n' + '|_ \n' + ' ')).toBe('2') + expect(convert(' _ \n' + ' _|\n' + '|_ \n' + ' ')).toBe('2') }) xit('recognizes three', () => { - expect(OcrParser.convert(' _ \n' + ' _|\n' + ' _|\n' + ' ')).toBe('3') + expect(convert(' _ \n' + ' _|\n' + ' _|\n' + ' ')).toBe('3') }) xit('recognizes four', () => { - expect(OcrParser.convert(' \n' + '|_|\n' + ' |\n' + ' ')).toBe('4') + expect(convert(' \n' + '|_|\n' + ' |\n' + ' ')).toBe('4') }) xit('recognizes five', () => { - expect(OcrParser.convert(' _ \n' + '|_ \n' + ' _|\n' + ' ')).toBe('5') + expect(convert(' _ \n' + '|_ \n' + ' _|\n' + ' ')).toBe('5') }) xit('recognizes six', () => { - expect(OcrParser.convert(' _ \n' + '|_ \n' + '|_|\n' + ' ')).toBe('6') + expect(convert(' _ \n' + '|_ \n' + '|_|\n' + ' ')).toBe('6') }) xit('recognizes seven', () => { - expect(OcrParser.convert(' _ \n' + ' |\n' + ' |\n' + ' ')).toBe('7') + expect(convert(' _ \n' + ' |\n' + ' |\n' + ' ')).toBe('7') }) xit('recognizes eight', () => { - expect(OcrParser.convert(' _ \n' + '|_|\n' + '|_|\n' + ' ')).toBe('8') + expect(convert(' _ \n' + '|_|\n' + '|_|\n' + ' ')).toBe('8') }) xit('recognizes nine', () => { - expect(OcrParser.convert(' _ \n' + '|_|\n' + ' _|\n' + ' ')).toBe('9') + expect(convert(' _ \n' + '|_|\n' + ' _|\n' + ' ')).toBe('9') }) xit('recognizes ten', () => { - expect( - OcrParser.convert(' _ \n' + ' || |\n' + ' ||_|\n' + ' ') - ).toBe('10') + expect(convert(' _ \n' + ' || |\n' + ' ||_|\n' + ' ')).toBe('10') }) xit('identifies garble', () => { - expect(OcrParser.convert(' \n' + '| |\n' + '| |\n' + ' ')).toBe('?') + expect(convert(' \n' + '| |\n' + '| |\n' + ' ')).toBe('?') }) xit('converts 110101100', () => { expect( - OcrParser.convert( + convert( ' _ _ _ _ \n' + ' | || | || | | || || |\n' + ' | ||_| ||_| | ||_||_|\n' + @@ -64,7 +62,7 @@ describe('ocr', () => { xit('identifies garble mixed in', () => { expect( - OcrParser.convert( + convert( ' _ _ _ \n' + ' | || | || | || || |\n' + ' | | _| ||_| | ||_||_|\n' + @@ -75,7 +73,7 @@ describe('ocr', () => { xit('converts 1234567890', () => { expect( - OcrParser.convert( + convert( ' _ _ _ _ _ _ _ _ \n' + ' | _| _||_||_ |_ ||_||_|| |\n' + ' ||_ _| | _||_| ||_| _||_|\n' + @@ -86,7 +84,7 @@ describe('ocr', () => { xit('converts 123 456 789', () => { expect( - OcrParser.convert( + convert( ' _ _ \n' + ' | _| _|\n' + ' ||_ _|\n' + diff --git a/exercises/practice/ocr-numbers/ocr-numbers.ts b/exercises/practice/ocr-numbers/ocr-numbers.ts index bddcbc254..dd68a0921 100644 --- a/exercises/practice/ocr-numbers/ocr-numbers.ts +++ b/exercises/practice/ocr-numbers/ocr-numbers.ts @@ -1 +1,3 @@ -export default class OcrParser {} +export function convert(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/palindrome-products/palindrome-products.example.ts b/exercises/practice/palindrome-products/.meta/proof.ci.ts similarity index 93% rename from exercises/practice/palindrome-products/palindrome-products.example.ts rename to exercises/practice/palindrome-products/.meta/proof.ci.ts index 586f05e41..6feb85683 100644 --- a/exercises/practice/palindrome-products/palindrome-products.example.ts +++ b/exercises/practice/palindrome-products/.meta/proof.ci.ts @@ -2,16 +2,18 @@ interface Input { maxFactor: number minFactor?: number } + interface Palindrome { value: number factors: Array<[number, number]> } + interface Output { largest: Palindrome smallest: Palindrome } -function generate({ maxFactor, minFactor = 1 }: Input): Output { +export function generate({ maxFactor, minFactor = 1 }: Input): Output { const factors = Array.from( { length: maxFactor - minFactor + 1 }, (_, k) => k + minFactor @@ -51,5 +53,3 @@ function isPalidrome(x: number): boolean { return a === b } - -export default generate diff --git a/exercises/practice/palindrome-products/palindrome-products.test.ts b/exercises/practice/palindrome-products/palindrome-products.test.ts index 6ff0bd03c..3eb1a018f 100644 --- a/exercises/practice/palindrome-products/palindrome-products.test.ts +++ b/exercises/practice/palindrome-products/palindrome-products.test.ts @@ -1,59 +1,135 @@ -import generate from './palindrome-products' +import { generate } from './palindrome-products' -describe('Palindrome', () => { - it('largest palindrome from single digit factors', () => { - const palindromes = generate({ maxFactor: 9 }) +describe('Palindromes', () => { + test('smallest palindrome from single digit factors', () => { + const palindromes = generate({ maxFactor: 9, minFactor: 1 }) + const smallest = palindromes.smallest + const expected = { value: 1, factors: [[1, 1]] } + + expect(smallest.value).toEqual(expected.value) + expect(sortFactors(smallest.factors)).toEqual(expected.factors) + }) + + xtest('largest palindrome from single digit factors', () => { + const palindromes = generate({ maxFactor: 9, minFactor: 1 }) const largest = palindromes.largest + const expected = { + value: 9, + factors: [ + [1, 9], + [3, 3], + ], + } - expect(largest.value).toEqual(9) - expect(largest.factors).toHaveLength(2) - expect(sort2dArray(largest.factors)).toEqual([ - [1, 9], - [3, 3], - ]) + expect(largest.value).toEqual(expected.value) + expect(sortFactors(largest.factors)).toEqual(expected.factors) }) - xit('largest palindrome from double digit factors', () => { + xtest('smallest palindrome from double digit factors', () => { const palindromes = generate({ maxFactor: 99, minFactor: 10 }) - const largest = palindromes.largest + const smallest = palindromes.smallest + const expected = { value: 121, factors: [[11, 11]] } - expect(largest.value).toEqual(9009) - expect(sort2dArray(largest.factors)).toEqual([[91, 99]]) + expect(smallest.value).toEqual(expected.value) + expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) - xit('smallest palindrome from double digit factors', () => { + xtest('largest palindrome from double digit factors', () => { const palindromes = generate({ maxFactor: 99, minFactor: 10 }) + const largest = palindromes.largest + const expected = { value: 9009, factors: [[91, 99]] } + + expect(largest.value).toEqual(expected.value) + expect(sortFactors(largest.factors)).toEqual(expected.factors) + }) + + xtest('smallest palindrome from triple digit factors', () => { + const palindromes = generate({ + maxFactor: 999, + minFactor: 100, + }) + const smallest = palindromes.smallest + const expected = { value: 10201, factors: [[101, 101]] } + + expect(smallest.value).toEqual(expected.value) + expect(sortFactors(smallest.factors)).toEqual(expected.factors) + }) + + xtest('largest palindrome from triple digit factors', () => { + const palindromes = generate({ + maxFactor: 999, + minFactor: 100, + }) + const largest = palindromes.largest + const expected = { value: 906609, factors: [[913, 993]] } + + expect(largest.value).toEqual(expected.value) + expect(sortFactors(largest.factors)).toEqual(expected.factors) + }) + + xtest('smallest palindrome from four digit factors', () => { + const palindromes = generate({ + maxFactor: 9999, + minFactor: 1000, + }) const smallest = palindromes.smallest + const expected = { value: 1002001, factors: [[1001, 1001]] } - expect(smallest.value).toEqual(121) - expect(sort2dArray(smallest.factors)).toEqual([[11, 11]]) + expect(smallest.value).toEqual(expected.value) + expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) - xit('largest palindrome from triple digit factors', () => { - const palindromes = generate({ maxFactor: 999, minFactor: 100 }) + test.skip('largest palindrome from four digit factors', () => { + const palindromes = generate({ + maxFactor: 9999, + minFactor: 1000, + }) const largest = palindromes.largest + const expected = { value: 99000099, factors: [[9901, 9999]] } - expect(largest.value).toEqual(906609) - expect(sort2dArray(largest.factors)).toEqual([[913, 993]]) + expect(largest.value).toEqual(expected.value) + expect(sortFactors(largest.factors)).toEqual(expected.factors) }) - xit('smallest palindrome from triple digit factors', () => { - const palindromes = generate({ maxFactor: 999, minFactor: 100 }) + xtest('empty result for smallest if no palindrome in range', () => { + const palindromes = generate({ + maxFactor: 1003, + minFactor: 1002, + }) const smallest = palindromes.smallest - expect(smallest.value).toEqual(10201) - expect(sort2dArray(smallest.factors)).toEqual([[101, 101]]) + expect(smallest.value).toBe(null) + expect(smallest.factors).toEqual([]) + }) + + xtest('empty result for largest if no palindrome in range', () => { + const palindromes = generate({ maxFactor: 15, minFactor: 15 }) + const largest = palindromes.largest + + expect(largest.value).toBe(null) + expect(largest.factors).toEqual([]) + }) + + xtest('error for smallest if min is more than max', () => { + expect(() => { + const palindromes = generate({ + maxFactor: 1, + minFactor: 10000, + }) + palindromes.smallest + }).toThrow(new Error('min must be <= max')) + }) + + xtest('error for largest if min is more than max', () => { + expect(() => { + const palindromes = generate({ maxFactor: 1, minFactor: 2 }) + palindromes.largest + }).toThrow(new Error('min must be <= max')) }) }) -// This conversion may seem a bit convoluted, but it allows a broader set of solutions -// and it helps by being a bit more specific about what values were expected. -function sort2dArray(input: T[][]): number[][] { - return Array.from(input) - .map((subArray: T[]): number[] => - Array.from(subArray) - .map((el) => Number(el)) - .sort() - ) - .sort() +function sortFactors( + factors: ReturnType['smallest']['factors'] +) { + return factors.map((f) => f.sort()).sort() } diff --git a/exercises/practice/palindrome-products/palindrome-products.ts b/exercises/practice/palindrome-products/palindrome-products.ts index e69de29bb..3ff5504c6 100644 --- a/exercises/practice/palindrome-products/palindrome-products.ts +++ b/exercises/practice/palindrome-products/palindrome-products.ts @@ -0,0 +1,18 @@ +interface Input { + maxFactor: number + minFactor?: number +} + +interface Palindrome { + value: number + factors: Array<[number, number]> +} + +interface Output { + largest: Palindrome + smallest: Palindrome +} + +export function generate(input: Input): Output { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/pangram/.meta/proof.ci.ts b/exercises/practice/pangram/.meta/proof.ci.ts new file mode 100644 index 000000000..e4b975a30 --- /dev/null +++ b/exercises/practice/pangram/.meta/proof.ci.ts @@ -0,0 +1,22 @@ +const aToZ = [...Array(26)].map((_, index) => { + return String.fromCharCode(index + 65) +}) + +export function isPangram(): boolean { + const myMap = new Map() + aToZ.forEach((key: string) => { + myMap.set(key.toLowerCase(), 0) + }) + + for (const each of this.value) { + const value = myMap.get(each) || 0 + myMap.set(each.toLowerCase(), value + 1) + } + + for (const each of myMap.values()) { + if (each === 0) { + return false + } + } + return true +} diff --git a/exercises/practice/pangram/pangram.example.ts b/exercises/practice/pangram/pangram.example.ts deleted file mode 100644 index cfc749397..000000000 --- a/exercises/practice/pangram/pangram.example.ts +++ /dev/null @@ -1,32 +0,0 @@ -const aToZ = [...Array(26)].map((_, index) => { - return String.fromCharCode(index + 65) -}) - -class Pangram { - private readonly value: string - constructor(input: string = '') { - this.value = input - } - - public isPangram(): boolean { - // create dictionay and fill it with the alphabet - const myMap = new Map() - aToZ.forEach((key: string) => { - myMap.set(key.toLowerCase(), 0) - }) - - for (const each of this.value) { - const value = myMap.get(each) || 0 - myMap.set(each.toLowerCase(), value + 1) - } - - for (const each of myMap.values()) { - if (each === 0) { - return false - } - } - return true - } -} - -export default Pangram diff --git a/exercises/practice/pangram/pangram.test.ts b/exercises/practice/pangram/pangram.test.ts index 1af1b52fb..51fce8e9b 100644 --- a/exercises/practice/pangram/pangram.test.ts +++ b/exercises/practice/pangram/pangram.test.ts @@ -1,54 +1,47 @@ -import Pangram from './pangram' +import { isPangram } from './pangram' describe('Pangram()', () => { - it('empty sentence', () => { - const pangram = new Pangram('') - expect(pangram.isPangram()).toBe(false) + test('empty sentence', () => { + expect(isPangram('')).toBe(false) }) - xit('pangram with only lower case', () => { - const pangram = new Pangram('the quick brown fox jumps over the lazy dog') - expect(pangram.isPangram()).toBe(true) + xtest('perfect lower case', () => { + expect(isPangram('abcdefghijklmnopqrstuvwxyz')).toBe(true) }) - xit("missing character 'x'", () => { - const pangram = new Pangram( - 'a quick movement of the enemy will jeopardize five gunboats' - ) - expect(pangram.isPangram()).toBe(false) + xtest('only lower case', () => { + expect(isPangram('the quick brown fox jumps over the lazy dog')).toBe(true) + }) + + xtest("missing the letter 'x'", () => { + expect( + isPangram('a quick movement of the enemy will jeopardize five gunboats') + ).toBe(false) }) - xit("another missing character 'x'", () => { - const pangram = new Pangram('the quick brown fish jumps over the lazy dog') - expect(pangram.isPangram()).toBe(false) + xtest("missing the letter 'h'", () => { + expect(isPangram('five boxing wizards jump quickly at it')).toBe(false) }) - xit('pangram with underscores', () => { - const pangram = new Pangram('the_quick_brown_fox_jumps_over_the_lazy_dog') - expect(pangram.isPangram()).toBe(true) + xtest('with underscores', () => { + expect(isPangram('the_quick_brown_fox_jumps_over_the_lazy_dog')).toBe(true) }) - xit('pangram with numbers', () => { - const pangram = new Pangram( - 'the 1 quick brown fox jumps over the 2 lazy dogs' + xtest('with numbers', () => { + expect(isPangram('the 1 quick brown fox jumps over the 2 lazy dogs')).toBe( + true ) - expect(pangram.isPangram()).toBe(true) }) - xit('missing letters replaced by numbers', () => { - const pangram = new Pangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog') - expect(pangram.isPangram()).toBe(false) + xtest('missing letters replaced by numbers', () => { + expect(isPangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog')).toBe(false) }) - xit('pangram with mixed case and punctuation', () => { - const pangram = new Pangram('"Five quacking Zephyrs jolt my wax bed."') - expect(pangram.isPangram()).toBe(true) + xtest('mixed case and punctuation', () => { + expect(isPangram('"Five quacking Zephyrs jolt my wax bed."')).toBe(true) }) - xit('pangram with non-ascii characters', () => { - const pangram = new Pangram( - 'Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich.' - ) - expect(pangram.isPangram()).toBe(true) + xtest('case insensitive', () => { + expect(isPangram('the quick brown fox jumps over with lazy FX')).toBe(false) }) }) diff --git a/exercises/practice/pangram/pangram.ts b/exercises/practice/pangram/pangram.ts index e69de29bb..fd44e7755 100644 --- a/exercises/practice/pangram/pangram.ts +++ b/exercises/practice/pangram/pangram.ts @@ -0,0 +1,3 @@ +export function isPangram() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/pascals-triangle/pascals-triangle.example.ts b/exercises/practice/pascals-triangle/.meta/proof.ci.ts similarity index 95% rename from exercises/practice/pascals-triangle/pascals-triangle.example.ts rename to exercises/practice/pascals-triangle/.meta/proof.ci.ts index 8fe01d0d7..b80e60bad 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle.example.ts +++ b/exercises/practice/pascals-triangle/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default class Triangle { +export class Triangle { public readonly rows: number[][] public readonly lastRow: number[] diff --git a/exercises/practice/pascals-triangle/pascals-triangle.test.ts b/exercises/practice/pascals-triangle/pascals-triangle.test.ts index e4ec4174a..d27a7eae8 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle.test.ts +++ b/exercises/practice/pascals-triangle/pascals-triangle.test.ts @@ -1,4 +1,4 @@ -import Triangle from './pascals-triangle' +import { Triangle } from './pascals-triangle' describe('Triangle', () => { it('with one row', () => { diff --git a/exercises/practice/pascals-triangle/pascals-triangle.ts b/exercises/practice/pascals-triangle/pascals-triangle.ts index 2c98a3a5d..e2877a650 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle.ts +++ b/exercises/practice/pascals-triangle/pascals-triangle.ts @@ -1 +1 @@ -export default class Triangle {} +export class Triangle {} diff --git a/exercises/practice/perfect-numbers/.meta/proof.ci.ts b/exercises/practice/perfect-numbers/.meta/proof.ci.ts new file mode 100644 index 000000000..c3f9aabcf --- /dev/null +++ b/exercises/practice/perfect-numbers/.meta/proof.ci.ts @@ -0,0 +1,51 @@ +export function classify(n: number): string { + let i + let sum + let result + + // Check if the input is valid + if (n <= 0) { + throw new Error('Classification is only possible for natural numbers.') + } + + // Factorize the current number. + const divsArray = getDivisors(n) + + // Sum the factors. + sum = 0 + for (i = 0; i < divsArray.length; i++) { + sum = sum + divsArray[i] + } + + // Check if the number is perfect. + if (sum === n) { + result = 'perfect' + } else if (sum > n) { + result = 'abundant' + } else { + result = 'deficient' + } + + return result +} + +function getDivisors(n: number): number[] { + const divs: number[] = [] + + // Accepts only natural numbers greater than 1. + if (n <= 1) { + return divs + } + + // 1 always divides everyone! + divs.push(1) + + // Calculate the divisors up the the half of the number + 1 + for (let i = 2; i <= n / 2; i++) { + if (n % i === 0) { + divs.push(i) + } + } + + return divs +} diff --git a/exercises/practice/perfect-numbers/perfect-numbers.example.ts b/exercises/practice/perfect-numbers/perfect-numbers.example.ts deleted file mode 100644 index c484039b6..000000000 --- a/exercises/practice/perfect-numbers/perfect-numbers.example.ts +++ /dev/null @@ -1,55 +0,0 @@ -class PerfectNumbers { - public static classify(n: number): string { - let i - let sum - let result - - // Check if the input is valid - if (n <= 0) { - throw new Error('Classification is only possible for natural numbers.') - } - - // Factorize the current number. - const divsArray = this.getDivisors(n) - - // Sum the factors. - sum = 0 - for (i = 0; i < divsArray.length; i++) { - sum = sum + divsArray[i] - } - - // Check if the number is perfect. - if (sum === n) { - result = 'perfect' - } else if (sum > n) { - result = 'abundant' - } else { - result = 'deficient' - } - - return result - } - - private static getDivisors(n: number): number[] { - const divs: number[] = [] - - // Accepts only natural numbers greater than 1. - if (n <= 1) { - return divs - } - - // 1 always divides everyone! - divs.push(1) - - // Calculate the divisors up the the half of the number + 1 - for (let i = 2; i <= n / 2; i++) { - if (n % i === 0) { - divs.push(i) - } - } - - return divs - } -} - -export default PerfectNumbers diff --git a/exercises/practice/perfect-numbers/perfect-numbers.test.ts b/exercises/practice/perfect-numbers/perfect-numbers.test.ts index d9b9915bc..e71af1047 100644 --- a/exercises/practice/perfect-numbers/perfect-numbers.test.ts +++ b/exercises/practice/perfect-numbers/perfect-numbers.test.ts @@ -1,76 +1,76 @@ -import PerfectNumbers from './perfect-numbers' +import { classify } from './perfect-numbers' describe('Perfect numbers', () => { it('Smallest perfect number is classified correctly', () => { const expected = 'perfect' - expect(PerfectNumbers.classify(6)).toEqual(expected) + expect(classify(6)).toEqual(expected) }) xit('Medium perfect number is classified correctly', () => { const expected = 'perfect' - expect(PerfectNumbers.classify(28)).toEqual(expected) + expect(classify(28)).toEqual(expected) }) xit('Large perfect number is classified correctly', () => { const expected = 'perfect' - expect(PerfectNumbers.classify(33550336)).toEqual(expected) + expect(classify(33550336)).toEqual(expected) }) }) describe('Abundant numbers', () => { xit('Smallest abundant number is classified correctly', () => { const expected = 'abundant' - expect(PerfectNumbers.classify(12)).toEqual(expected) + expect(classify(12)).toEqual(expected) }) xit('Medium abundant number is classified correctly', () => { const expected = 'abundant' - expect(PerfectNumbers.classify(30)).toEqual(expected) + expect(classify(30)).toEqual(expected) }) xit('Large abundant number is classified correctly', () => { const expected = 'abundant' - expect(PerfectNumbers.classify(33550335)).toEqual(expected) + expect(classify(33550335)).toEqual(expected) }) }) describe('Deficient numbers', () => { xit('Smallest prime deficient number is classified correctly', () => { const expected = 'deficient' - expect(PerfectNumbers.classify(2)).toEqual(expected) + expect(classify(2)).toEqual(expected) }) xit('Smallest non-prime deficient number is classified correctly', () => { const expected = 'deficient' - expect(PerfectNumbers.classify(4)).toEqual(expected) + expect(classify(4)).toEqual(expected) }) xit('Medium deficient number is classified correctly', () => { const expected = 'deficient' - expect(PerfectNumbers.classify(32)).toEqual(expected) + expect(classify(32)).toEqual(expected) }) xit('Large deficient number is classified correctly', () => { const expected = 'deficient' - expect(PerfectNumbers.classify(33550337)).toEqual(expected) + expect(classify(33550337)).toEqual(expected) }) xit('Edge case (no factors other than itself) is classified correctly', () => { const expected = 'deficient' - expect(PerfectNumbers.classify(1)).toEqual(expected) + expect(classify(1)).toEqual(expected) }) }) describe('Invalid inputs', () => { xit('Zero is rejected (not a natural number)', () => { expect(() => { - PerfectNumbers.classify(0) + classify(0) }).toThrowError('Classification is only possible for natural numbers.') }) xit('Negative integer is rejected (not a natural number)', () => { expect(() => { - PerfectNumbers.classify(-1) + classify(-1) }).toThrowError('Classification is only possible for natural numbers.') }) }) diff --git a/exercises/practice/perfect-numbers/perfect-numbers.ts b/exercises/practice/perfect-numbers/perfect-numbers.ts index e69de29bb..0a8c67b23 100644 --- a/exercises/practice/perfect-numbers/perfect-numbers.ts +++ b/exercises/practice/perfect-numbers/perfect-numbers.ts @@ -0,0 +1,3 @@ +export function classify() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/phone-number/.meta/proof.ci.ts b/exercises/practice/phone-number/.meta/proof.ci.ts new file mode 100644 index 000000000..e6e919c28 --- /dev/null +++ b/exercises/practice/phone-number/.meta/proof.ci.ts @@ -0,0 +1,40 @@ +export const clean = (number: string) => { + if (/[a-zA-Z]/.test(number)) { + throw new Error('Letters not permitted') + } else if (/[@:!]/.test(number)) { + throw new Error('Punctuations not permitted') + } + + let strippedNumber = number.replace(/\D/g, '') + const numberLength = strippedNumber.length + + if (numberLength === 11) { + if (strippedNumber.substring(0, 1) !== '1') { + throw new Error('11 digits must start with 1') + } else { + strippedNumber = strippedNumber.substring(1) + } + } + + if (numberLength < 10) { + throw new Error('Incorrect number of digits') + } + + if (numberLength > 11) { + throw new Error('More than 11 digits') + } + + if (strippedNumber.substring(0, 1) === '0') { + throw new Error('Area code cannot start with zero') + } else if (strippedNumber.substring(0, 1) === '1') { + throw new Error('Area code cannot start with one') + } + + if (strippedNumber.substring(3, 4) === '0') { + throw new Error('Exchange code cannot start with zero') + } else if (strippedNumber.substring(3, 4) === '1') { + throw new Error('Exchange code cannot start with one') + } + + return strippedNumber +} diff --git a/exercises/practice/phone-number/phone-number.example.ts b/exercises/practice/phone-number/phone-number.example.ts deleted file mode 100644 index bd39388a8..000000000 --- a/exercises/practice/phone-number/phone-number.example.ts +++ /dev/null @@ -1,25 +0,0 @@ -class PhoneNumber { - private value: string - - constructor(input: string) { - let cleanedUp = input.replace(/(\)|\(|\s|\.|-)/g, '') - if (cleanedUp.length === 11 && cleanedUp[0] === '1') { - cleanedUp = cleanedUp.slice(1, 11) - } - this.value = cleanedUp - } - - public number(): string | undefined { - if (this.value.length < 10 || this.value.length > 10) { - return undefined - } - for (const each of this.value) { - const result = parseInt(each, 10) - if (isNaN(result)) { - return undefined - } - } - return this.value - } -} -export default PhoneNumber diff --git a/exercises/practice/phone-number/phone-number.test.ts b/exercises/practice/phone-number/phone-number.test.ts index 705ed2e97..4c548f050 100644 --- a/exercises/practice/phone-number/phone-number.test.ts +++ b/exercises/practice/phone-number/phone-number.test.ts @@ -1,53 +1,103 @@ -import PhoneNumber from './phone-number' +import { clean } from './phone-number' -describe('PhoneNumber()', () => { - it('cleans the number', () => { - const phone = new PhoneNumber('(223) 456-7890') - expect(phone.number()).toEqual('2234567890') - }) +describe('Phone Number', () => { + describe('Cleanup user-entered phone numbers', () => { + test('cleans the number', () => { + expect(clean('(223) 456-7890')).toEqual('2234567890') + }) - xit('cleans numbers with dots', () => { - const phone = new PhoneNumber('223.456.7890') - expect(phone.number()).toEqual('2234567890') - }) + xtest('cleans numbers with dots', () => { + expect(clean('223.456.7890')).toEqual('2234567890') + }) - xit('cleans numbers with multiple spaces', () => { - const phone = new PhoneNumber('223 456 7890 ') - expect(phone.number()).toEqual('2234567890') - }) + xtest('cleans numbers with multiple spaces', () => { + expect(clean('223 456 7890 ')).toEqual('2234567890') + }) - xit('invalid when 9 digits', () => { - const phone = new PhoneNumber('123456789') - expect(phone.number()).toEqual(undefined) - }) + xtest('invalid when 9 digits', () => { + expect(() => clean('123456789')).toThrow( + new Error('Incorrect number of digits') + ) + }) - xit('invalid when 11 digits', () => { - const phone = new PhoneNumber('21234567890') - expect(phone.number()).toEqual(undefined) - }) + xtest('invalid when 11 digits does not start with a 1', () => { + expect(() => clean('22234567890')).toThrow( + new Error('11 digits must start with 1') + ) + }) - xit('valid when 11 digits and starting with 1', () => { - const phone = new PhoneNumber('12234567890') - expect(phone.number()).toEqual('2234567890') - }) + xtest('valid when 11 digits and starting with 1', () => { + expect(clean('12234567890')).toEqual('2234567890') + }) - xit('invalid when 12 digits', () => { - const phone = new PhoneNumber('321234567890') - expect(phone.number()).toEqual(undefined) - }) + xtest('valid when 11 digits and starting with 1 even with punctuation', () => { + expect(clean('+1 (223) 456-7890')).toEqual('2234567890') + }) - xit('invalid with letters', () => { - const phone = new PhoneNumber('123-abc-7890') - expect(phone.number()).toEqual(undefined) - }) + xtest('invalid when more than 11 digits', () => { + expect(() => clean('321234567890')).toThrow( + new Error('More than 11 digits') + ) + }) - xit('invalid with punctuations', () => { - const phone = new PhoneNumber('123-@:!-7890') - expect(phone.number()).toEqual(undefined) - }) + xtest('invalid with letters', () => { + expect(() => clean('123-abc-7890')).toThrow( + new Error('Letters not permitted') + ) + }) + + xtest('invalid with punctuations', () => { + expect(() => clean('123-@:!-7890')).toThrow( + new Error('Punctuations not permitted') + ) + }) + + xtest('invalid if area code starts with 0', () => { + expect(() => clean('(023) 456-7890')).toThrow( + new Error('Area code cannot start with zero') + ) + }) + + xtest('invalid if area code starts with 1', () => { + expect(() => clean('(123) 456-7890')).toThrow( + new Error('Area code cannot start with one') + ) + }) + + xtest('invalid if exchange code starts with 0', () => { + expect(() => clean('(223) 056-7890')).toThrow( + new Error('Exchange code cannot start with zero') + ) + }) + + xtest('invalid if exchange code starts with 1', () => { + expect(() => clean('(223) 156-7890')).toThrow( + new Error('Exchange code cannot start with one') + ) + }) + + xtest('invalid if area code starts with 0 on valid 11-digit number', () => { + expect(() => clean('1 (023) 456-7890')).toThrow( + new Error('Area code cannot start with zero') + ) + }) + + xtest('invalid if area code starts with 1 on valid 11-digit number', () => { + expect(() => clean('1 (123) 456-7890')).toThrow( + new Error('Area code cannot start with one') + ) + }) + + xtest('invalid if exchange code starts with 0 on valid 11-digit number', () => { + expect(() => clean('1 (223) 056-7890')).toThrow( + new Error('Exchange code cannot start with zero') + ) + }) - xit('invalid with right number of digits but letters mixed in', () => { - const phone = new PhoneNumber('1a2b3c4d5e6f7g8h9i0j') - expect(phone.number()).toEqual(undefined) + xtest('invalid if exchange code starts with 1 on valid 11-digit number', () => { + expect(() => clean('1 (223) 156-7890')).toThrow( + new Error('Exchange code cannot start with one') + ) + }) }) }) diff --git a/exercises/practice/phone-number/phone-number.ts b/exercises/practice/phone-number/phone-number.ts index e69de29bb..67eced074 100644 --- a/exercises/practice/phone-number/phone-number.ts +++ b/exercises/practice/phone-number/phone-number.ts @@ -0,0 +1,3 @@ +export function clean() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/pig-latin/.meta/proof.ci.ts b/exercises/practice/pig-latin/.meta/proof.ci.ts new file mode 100644 index 000000000..b426a41cd --- /dev/null +++ b/exercises/practice/pig-latin/.meta/proof.ci.ts @@ -0,0 +1,18 @@ +export function translate(phrase: string): string { + const words = phrase.split(' ') + const translated: string[] = [] + + words.forEach((word) => { + const parts = word.match(/^([^aeiou]?qu|[^aeiou]*)(.+)/) + const beginning = parts![1] + const ending = parts![2] + + if (beginning.length === 0) { + translated.push(`${word}ay`) + } else { + translated.push(`${ending}${beginning}ay`) + } + }) + + return translated.join(' ') +} diff --git a/exercises/practice/pig-latin/pig-latin.example.ts b/exercises/practice/pig-latin/pig-latin.example.ts deleted file mode 100644 index e13c88548..000000000 --- a/exercises/practice/pig-latin/pig-latin.example.ts +++ /dev/null @@ -1,22 +0,0 @@ -class PigLatin { - public static translate(phrase: string): string { - const words = phrase.split(' ') - const translated: string[] = [] - - words.forEach((word) => { - const parts = word.match(/^([^aeiou]?qu|[^aeiou]*)(.+)/) - const beginning = parts![1] - const ending = parts![2] - - if (beginning.length === 0) { - translated.push(`${word}ay`) - } else { - translated.push(`${ending}${beginning}ay`) - } - }) - - return translated.join(' ') - } -} - -export default PigLatin diff --git a/exercises/practice/pig-latin/pig-latin.test.ts b/exercises/practice/pig-latin/pig-latin.test.ts index 7b49676b1..548c1964f 100644 --- a/exercises/practice/pig-latin/pig-latin.test.ts +++ b/exercises/practice/pig-latin/pig-latin.test.ts @@ -1,106 +1,106 @@ -import PigLatin from './pig-latin' +import { translate } from './pig-latin' describe('ay is added to words that start with vowels', () => { it('word beginning with a', () => { const expected = 'appleay' - expect(PigLatin.translate('apple')).toEqual(expected) + expect(translate('apple')).toEqual(expected) }) xit('word beginning with e', () => { const expected = 'earay' - expect(PigLatin.translate('ear')).toEqual(expected) + expect(translate('ear')).toEqual(expected) }) xit('word beginning with i', () => { const expected = 'iglooay' - expect(PigLatin.translate('igloo')).toEqual(expected) + expect(translate('igloo')).toEqual(expected) }) xit('word beginning with o', () => { const expected = 'objectay' - expect(PigLatin.translate('object')).toEqual(expected) + expect(translate('object')).toEqual(expected) }) xit('word beginning with u', () => { const expected = 'underay' - expect(PigLatin.translate('under')).toEqual(expected) + expect(translate('under')).toEqual(expected) }) xit('word beginning with a vowel and followed by a qu', () => { const expected = 'equalay' - expect(PigLatin.translate('equal')).toEqual(expected) + expect(translate('equal')).toEqual(expected) }) }) describe('first letter and ay are moved to the end of words that start with consonants', () => { xit('word beginning with p', () => { const expected = 'igpay' - expect(PigLatin.translate('pig')).toEqual(expected) + expect(translate('pig')).toEqual(expected) }) xit('word beginning with k', () => { const expected = 'oalakay' - expect(PigLatin.translate('koala')).toEqual(expected) + expect(translate('koala')).toEqual(expected) }) xit('word beginning with x', () => { const expected = 'enonxay' - expect(PigLatin.translate('xenon')).toEqual(expected) + expect(translate('xenon')).toEqual(expected) }) xit('word beginning with q without a following u', () => { const expected = 'atqay' - expect(PigLatin.translate('qat')).toEqual(expected) + expect(translate('qat')).toEqual(expected) }) }) describe('some letter clusters are treated like a single consonant', () => { xit('word beginning with ch', () => { const expected = 'airchay' - expect(PigLatin.translate('chair')).toEqual(expected) + expect(translate('chair')).toEqual(expected) }) xit('word beginning with qu', () => { const expected = 'eenquay' - expect(PigLatin.translate('queen')).toEqual(expected) + expect(translate('queen')).toEqual(expected) }) xit('word beginning with qu and a preceding consonant', () => { const expected = 'aresquay' - expect(PigLatin.translate('square')).toEqual(expected) + expect(translate('square')).toEqual(expected) }) xit('word beginning with th', () => { const expected = 'erapythay' - expect(PigLatin.translate('therapy')).toEqual(expected) + expect(translate('therapy')).toEqual(expected) }) xit('word beginning with thr', () => { const expected = 'ushthray' - expect(PigLatin.translate('thrush')).toEqual(expected) + expect(translate('thrush')).toEqual(expected) }) xit('word beginning with sch', () => { const expected = 'oolschay' - expect(PigLatin.translate('school')).toEqual(expected) + expect(translate('school')).toEqual(expected) }) }) describe('position of y in a word determines if it is a consonant or a vowel', () => { xit('y is treated like a consonant at the beginning of a word', () => { const expected = 'ellowyay' - expect(PigLatin.translate('yellow')).toEqual(expected) + expect(translate('yellow')).toEqual(expected) }) xit('y as second letter in two letter word', () => { const expected = 'ymay' - expect(PigLatin.translate('my')).toEqual(expected) + expect(translate('my')).toEqual(expected) }) }) describe('phrases are translated', () => { xit('a whole phrase', () => { const expected = 'ickquay astfay unray' - expect(PigLatin.translate('quick fast run')).toEqual(expected) + expect(translate('quick fast run')).toEqual(expected) }) }) diff --git a/exercises/practice/pig-latin/pig-latin.ts b/exercises/practice/pig-latin/pig-latin.ts index e69de29bb..b6ab6035a 100644 --- a/exercises/practice/pig-latin/pig-latin.ts +++ b/exercises/practice/pig-latin/pig-latin.ts @@ -0,0 +1,3 @@ +export function translate() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/prime-factors/prime-factors.example.ts b/exercises/practice/prime-factors/.meta/proof.ci.ts similarity index 78% rename from exercises/practice/prime-factors/prime-factors.example.ts rename to exercises/practice/prime-factors/.meta/proof.ci.ts index 5457e7740..3625a3c45 100644 --- a/exercises/practice/prime-factors/prime-factors.example.ts +++ b/exercises/practice/prime-factors/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default function calculatePrimeFactors(num: number): number[] { +export function calculatePrimeFactors(num: number): number[] { const factors = [] let currentFactor = 2 while (num !== 1) { diff --git a/exercises/practice/prime-factors/prime-factors.test.ts b/exercises/practice/prime-factors/prime-factors.test.ts index 5ac0d0755..f4e0912fb 100644 --- a/exercises/practice/prime-factors/prime-factors.test.ts +++ b/exercises/practice/prime-factors/prime-factors.test.ts @@ -1,4 +1,4 @@ -import calculatePrimeFactors from './prime-factors' +import { calculatePrimeFactors } from './prime-factors' describe('calculatePrimeFactors', () => { it('returns an empty array for 1', () => diff --git a/exercises/practice/prime-factors/prime-factors.ts b/exercises/practice/prime-factors/prime-factors.ts index e69de29bb..820e390fa 100644 --- a/exercises/practice/prime-factors/prime-factors.ts +++ b/exercises/practice/prime-factors/prime-factors.ts @@ -0,0 +1,3 @@ +export function calculatePrimeFactors() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/protein-translation/.meta/proof.ci.ts b/exercises/practice/protein-translation/.meta/proof.ci.ts new file mode 100644 index 000000000..d954cc712 --- /dev/null +++ b/exercises/practice/protein-translation/.meta/proof.ci.ts @@ -0,0 +1,45 @@ +const ACID_PROTEIN_MAP = { + AUG: 'Methionine', + UUU: 'Phenylalanine', + UUC: 'Phenylalanine', + UUA: 'Leucine', + UUG: 'Leucine', + UCU: 'Serine', + UCC: 'Serine', + UCA: 'Serine', + UCG: 'Serine', + UAU: 'Tyrosine', + UAC: 'Tyrosine', + UGU: 'Cysteine', + UGC: 'Cysteine', + UGG: 'Tryptophan', + UAA: 'STOP', + UAG: 'STOP', + UGA: 'STOP', +} + +const getProtein = (codon: string) => ACID_PROTEIN_MAP[codon] || 'INVALID' + +export const translate = (rnaStrand: string) => { + const proteins: typeof ACID_PROTEIN_MAP[keyof typeof ACID_PROTEIN_MAP][] = [] + + if (rnaStrand) { + for (let i = 0; i < rnaStrand.length; i += 3) { + const protein = getProtein(rnaStrand.substring(i, i + 3)) + + if (protein) { + if (protein === 'STOP') { + break + } + + if (protein === 'INVALID') { + throw new Error('Invalid codon') + } + + proteins.push(protein) + } + } + } + + return proteins +} diff --git a/exercises/practice/protein-translation/protein-translation.example.ts b/exercises/practice/protein-translation/protein-translation.example.ts deleted file mode 100644 index 2d530f0a2..000000000 --- a/exercises/practice/protein-translation/protein-translation.example.ts +++ /dev/null @@ -1,61 +0,0 @@ -class ProteinTranslation { - private static readonly translations = [ - { codon: 'AUG', protein: 'Methionine' }, - { codon: 'UUU', protein: 'Phenylalanine' }, - { codon: 'UUC', protein: 'Phenylalanine' }, - { codon: 'UUA', protein: 'Leucine' }, - { codon: 'UUG', protein: 'Leucine' }, - { codon: 'UCU', protein: 'Serine' }, - { codon: 'UCC', protein: 'Serine' }, - { codon: 'UCA', protein: 'Serine' }, - { codon: 'UCG', protein: 'Serine' }, - { codon: 'UAU', protein: 'Tyrosine' }, - { codon: 'UAC', protein: 'Tyrosine' }, - { codon: 'UGU', protein: 'Cysteine' }, - { codon: 'UGC', protein: 'Cysteine' }, - { codon: 'UGG', protein: 'Tryptophan' }, - { codon: 'UAA', protein: 'STOP' }, - { codon: 'UAG', protein: 'STOP' }, - { codon: 'UGA', protein: 'STOP' }, - ] - - public static proteins(strand: string): string[] { - const codons = this.breakStrandIntoCodons(strand) - return this.getProteinsFromCodons(codons) - } - - private static breakStrandIntoCodons(strand: string): string[] { - const result: string[] = [] - for (let i = 0; i <= strand.length - 1; i++) { - result.push(strand.slice(0, 3)) - strand = strand.slice(3) - } - return result - } - - private static getProteinsFromCodons(codons: string[]): string[] { - const result: string[] = [] - for (const codon of codons) { - const protein = this.getProteinFromCodon(codon) - if (protein !== 'STOP') { - result.push(protein) - } else { - break - } - } - return result - } - - private static getProteinFromCodon(codon: string): string { - let result = '' - for (const translation of this.translations) { - if (translation.codon === codon) { - result = translation.protein - break - } - } - return result - } -} - -export default ProteinTranslation diff --git a/exercises/practice/protein-translation/protein-translation.test.ts b/exercises/practice/protein-translation/protein-translation.test.ts index 6327c1c6c..4a1f9bd19 100644 --- a/exercises/practice/protein-translation/protein-translation.test.ts +++ b/exercises/practice/protein-translation/protein-translation.test.ts @@ -1,118 +1,118 @@ -import ProteinTranslation from './protein-translation' +import { translate } from './protein-translation' describe('Translate input RNA sequences into proteins', () => { it('Methionine RNA sequence', () => { const expected = ['Methionine'] - expect(ProteinTranslation.proteins('AUG')).toEqual(expected) + expect(translate('AUG')).toEqual(expected) }) xit('Phenylalanine RNA sequence 1', () => { const expected = ['Phenylalanine'] - expect(ProteinTranslation.proteins('UUU')).toEqual(expected) + expect(translate('UUU')).toEqual(expected) }) xit('Phenylalanine RNA sequence 2', () => { const expected = ['Phenylalanine'] - expect(ProteinTranslation.proteins('UUC')).toEqual(expected) + expect(translate('UUC')).toEqual(expected) }) xit('Leucine RNA sequence 1', () => { const expected = ['Leucine'] - expect(ProteinTranslation.proteins('UUA')).toEqual(expected) + expect(translate('UUA')).toEqual(expected) }) xit('Leucine RNA sequence 2', () => { const expected = ['Leucine'] - expect(ProteinTranslation.proteins('UUG')).toEqual(expected) + expect(translate('UUG')).toEqual(expected) }) xit('Serine RNA sequence 1', () => { const expected = ['Serine'] - expect(ProteinTranslation.proteins('UCU')).toEqual(expected) + expect(translate('UCU')).toEqual(expected) }) xit('Serine RNA sequence 2', () => { const expected = ['Serine'] - expect(ProteinTranslation.proteins('UCC')).toEqual(expected) + expect(translate('UCC')).toEqual(expected) }) xit('Serine RNA sequence 3', () => { const expected = ['Serine'] - expect(ProteinTranslation.proteins('UCA')).toEqual(expected) + expect(translate('UCA')).toEqual(expected) }) xit('Serine RNA sequence 4', () => { const expected = ['Serine'] - expect(ProteinTranslation.proteins('UCG')).toEqual(expected) + expect(translate('UCG')).toEqual(expected) }) xit('Tyrosine RNA sequence 1', () => { const expected = ['Tyrosine'] - expect(ProteinTranslation.proteins('UAU')).toEqual(expected) + expect(translate('UAU')).toEqual(expected) }) xit('Tyrosine RNA sequence 2', () => { const expected = ['Tyrosine'] - expect(ProteinTranslation.proteins('UAC')).toEqual(expected) + expect(translate('UAC')).toEqual(expected) }) xit('Cysteine RNA sequence 1', () => { const expected = ['Cysteine'] - expect(ProteinTranslation.proteins('UGU')).toEqual(expected) + expect(translate('UGU')).toEqual(expected) }) xit('Cysteine RNA sequence 2', () => { const expected = ['Cysteine'] - expect(ProteinTranslation.proteins('UGC')).toEqual(expected) + expect(translate('UGC')).toEqual(expected) }) xit('Tryptophan RNA sequence', () => { const expected = ['Tryptophan'] - expect(ProteinTranslation.proteins('UGG')).toEqual(expected) + expect(translate('UGG')).toEqual(expected) }) xit('STOP codon RNA sequence 1', () => { const expected: string[] = [] - expect(ProteinTranslation.proteins('UAA')).toEqual(expected) + expect(translate('UAA')).toEqual(expected) }) xit('STOP codon RNA sequence 2', () => { const expected: string[] = [] - expect(ProteinTranslation.proteins('UAG')).toEqual(expected) + expect(translate('UAG')).toEqual(expected) }) xit('STOP codon RNA sequence 3', () => { const expected: string[] = [] - expect(ProteinTranslation.proteins('UGA')).toEqual(expected) + expect(translate('UGA')).toEqual(expected) }) xit('Translate RNA strand into correct protein list', () => { const expected = ['Methionine', 'Phenylalanine', 'Tryptophan'] - expect(ProteinTranslation.proteins('AUGUUUUGG')).toEqual(expected) + expect(translate('AUGUUUUGG')).toEqual(expected) }) xit('Translation stops if STOP codon at beginning of sequence', () => { const expected: string[] = [] - expect(ProteinTranslation.proteins('UAGUGG')).toEqual(expected) + expect(translate('UAGUGG')).toEqual(expected) }) xit('Translation stops if STOP codon at end of two-codon sequence', () => { const expected = ['Tryptophan'] - expect(ProteinTranslation.proteins('UGGUAG')).toEqual(expected) + expect(translate('UGGUAG')).toEqual(expected) }) xit('Translation stops if STOP codon at end of three-codon sequence', () => { const expected = ['Methionine', 'Phenylalanine'] - expect(ProteinTranslation.proteins('AUGUUUUAA')).toEqual(expected) + expect(translate('AUGUUUUAA')).toEqual(expected) }) xit('Translation stops if STOP codon in middle of three-codon sequence', () => { const expected = ['Tryptophan'] - expect(ProteinTranslation.proteins('UGGUAGUGG')).toEqual(expected) + expect(translate('UGGUAGUGG')).toEqual(expected) }) xit('Translation stops if STOP codon in middle of six-codon sequence', () => { const expected = ['Tryptophan', 'Cysteine', 'Tyrosine'] - expect(ProteinTranslation.proteins('UGGUGUUAUUAAUGGUUU')).toEqual(expected) + expect(translate('UGGUGUUAUUAAUGGUUU')).toEqual(expected) }) }) diff --git a/exercises/practice/protein-translation/protein-translation.ts b/exercises/practice/protein-translation/protein-translation.ts index f7a97c6ca..b6ab6035a 100644 --- a/exercises/practice/protein-translation/protein-translation.ts +++ b/exercises/practice/protein-translation/protein-translation.ts @@ -1,7 +1,3 @@ -class ProteinTranslation { - static proteins(/* Parameters go here */) { - // Your code here - } +export function translate() { + throw new Error('Remove this statement and implement this function') } - -export default ProteinTranslation diff --git a/exercises/practice/proverb/proverb.example.ts b/exercises/practice/proverb/.meta/proof.ci.ts similarity index 82% rename from exercises/practice/proverb/proverb.example.ts rename to exercises/practice/proverb/.meta/proof.ci.ts index bc7432ff4..d80caf0a9 100644 --- a/exercises/practice/proverb/proverb.example.ts +++ b/exercises/practice/proverb/.meta/proof.ci.ts @@ -1,7 +1,7 @@ const conclusion = (firstArg: string): string => `And all for the want of a ${firstArg}.` -const proverb = (...args: string[]): string => { +export const proverb = (...args: string[]): string => { const allExceptLastArg = args.slice(0, -1) const chainOfEvents = allExceptLastArg.map( (arg, index) => `For want of a ${arg} the ${args[index + 1]} was lost.` @@ -11,5 +11,3 @@ const proverb = (...args: string[]): string => { return chainOfEvents.join('\n') } - -export default proverb diff --git a/exercises/practice/proverb/proverb.test.ts b/exercises/practice/proverb/proverb.test.ts index 4f4d027bf..16a531b8e 100644 --- a/exercises/practice/proverb/proverb.test.ts +++ b/exercises/practice/proverb/proverb.test.ts @@ -1,34 +1,34 @@ -import Proverb from './proverb' +import { proverb } from './proverb' describe('Proverb', () => { it('a single consequence', () => { const expected = `For want of a nail the shoe was lost.\nAnd all for the want of a nail.` - expect(Proverb('nail', 'shoe')).toEqual(expected) + expect(proverb('nail', 'shoe')).toEqual(expected) }) xit('a short chain of consequences', () => { const expected = `For want of a nail the shoe was lost.\nFor want of a shoe the horse was lost.\nAnd all for the want of a nail.` - expect(Proverb('nail', 'shoe', 'horse')).toEqual(expected) + expect(proverb('nail', 'shoe', 'horse')).toEqual(expected) }) xit('a longer chain of consequences', () => { const expected = `For want of a nail the shoe was lost.\nFor want of a shoe the horse was lost.\nFor want of a horse the rider was lost.\nAnd all for the want of a nail.` - expect(Proverb('nail', 'shoe', 'horse', 'rider')).toEqual(expected) + expect(proverb('nail', 'shoe', 'horse', 'rider')).toEqual(expected) }) xit('proverb function does not hard code the rhyme dictionary', () => { const expected = `For want of a key the value was lost.\nAnd all for the want of a key.` - expect(Proverb('key', 'value')).toEqual(expected) + expect(proverb('key', 'value')).toEqual(expected) }) xit('the whole proveb', () => { const expected = `For want of a nail the shoe was lost.\nFor want of a shoe the horse was lost.\nFor want of a horse the rider was lost.\nFor want of a rider the message was lost.\nFor want of a message the battle was lost.\nFor want of a battle the kingdom was lost.\nAnd all for the want of a nail.` expect( - Proverb('nail', 'shoe', 'horse', 'rider', 'message', 'battle', 'kingdom') + proverb('nail', 'shoe', 'horse', 'rider', 'message', 'battle', 'kingdom') ).toEqual(expected) }) xit('proverb is the same each time', () => { - expect(Proverb('nail', 'shoe')).toEqual(Proverb('nail', 'shoe')) + expect(proverb('nail', 'shoe')).toEqual(proverb('nail', 'shoe')) }) }) diff --git a/exercises/practice/proverb/proverb.ts b/exercises/practice/proverb/proverb.ts index e69de29bb..12624894e 100644 --- a/exercises/practice/proverb/proverb.ts +++ b/exercises/practice/proverb/proverb.ts @@ -0,0 +1,3 @@ +export function proverb() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts b/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts new file mode 100644 index 000000000..b1d24a20c --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts @@ -0,0 +1,50 @@ +type Options = { + minFactor?: number + maxFactor?: number + sum: number +} + +class Triplet { + constructor( + private readonly a: number, + private readonly b: number, + private readonly c: number + ) {} + + toArray() { + return [this.a, this.b, this.c] + } + + get pythagorean() { + return this.a * this.a + this.b * this.b === this.c * this.c + } + + get sum() { + return this.a + this.b + this.c + } +} + +export function triplets({ minFactor, maxFactor, sum }: Options) { + const min = minFactor || 1 + const max = maxFactor || sum - 1 + + const isDesired = (triplet: Triplet) => { + return triplet.pythagorean && (!sum || triplet.sum === sum) + } + + const triplets = [] + + for (let a = min; a < max - 1; a += 1) { + for (let b = a + 1; b < max; b += 1) { + for (let c = b + 1; c <= max; c += 1) { + const triplet = new Triplet(a, b, c) + + if (isDesired(triplet)) { + triplets.push(triplet) + } + } + } + } + + return triplets +} diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet.example.ts b/exercises/practice/pythagorean-triplet/pythagorean-triplet.example.ts deleted file mode 100644 index 214a36d6e..000000000 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet.example.ts +++ /dev/null @@ -1,69 +0,0 @@ -interface WhereOptions { - maxFactor: number - minFactor?: number - sum?: number -} - -export default class Triplet { - private readonly a: number - private readonly b: number - private readonly c: number - - constructor(a: number, b: number, c: number) { - this.a = a - this.b = b - this.c = c - } - - public isPythagorean(): boolean { - return this.a * this.a + this.b * this.b === this.c * this.c - } - - public sum(): number { - return this.a + this.b + this.c - } - - public product(): number { - return this.a * this.b * this.c - } - - public static where(options: WhereOptions): Triplet[] { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return new Triplets( - options.maxFactor, - options.minFactor, - options.sum - ).toArray() - } -} - -class Triplets { - private readonly min: number - private readonly max: number - private readonly sum?: number - - constructor(maxFactor: number, minFactor: number = 1, sum?: number) { - this.max = maxFactor - this.min = minFactor - this.sum = sum - } - - public toArray(): Triplet[] { - const triplets = [] - for (let a = this.min; a < this.max - 1; a++) { - for (let b = a + 1; b < this.max; b++) { - for (let c = b + 1; c <= this.max; c++) { - const triplet = new Triplet(a, b, c) - if (this.isDesired(triplet)) { - triplets.push(triplet) - } - } - } - } - return triplets - } - - private isDesired(triplet: Triplet): boolean { - return triplet.isPythagorean() && (!this.sum || triplet.sum() === this.sum) - } -} diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts b/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts index 5ba7f33d4..6a08682a0 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts @@ -1,43 +1,70 @@ -import Triplet from './pythagorean-triplet' +import { triplets } from './pythagorean-triplet' + +function tripletsWithSum(sum: number, options = {}) { + return triplets({ ...options, sum }).map((triplet) => + triplet.toArray().sort((a, b) => a - b) + ) +} describe('Triplet', () => { - it('calculates the sum', () => { - expect(new Triplet(3, 4, 5).sum()).toBe(12) + test('triplets whose sum is 12', () => { + expect(tripletsWithSum(12)).toEqual([[3, 4, 5]]) + }) + + xtest('triplets whose sum is 108', () => { + expect(tripletsWithSum(108)).toEqual([[27, 36, 45]]) + }) + + xtest('triplets whose sum is 1000', () => { + expect(tripletsWithSum(1000)).toEqual([[200, 375, 425]]) + }) + + xtest('no matching triplets for 1001', () => { + expect(tripletsWithSum(1001)).toEqual([]) }) - xit('calculates the product', () => { - expect(new Triplet(3, 4, 5).product()).toBe(60) + xtest('returns all matching triplets', () => { + expect(tripletsWithSum(90)).toEqual([ + [9, 40, 41], + [15, 36, 39], + ]) }) - xit('can recognize a pythagorean triplet', () => { - expect(new Triplet(3, 4, 5).isPythagorean()).toBe(true) + xtest('several matching triplets', () => { + expect(tripletsWithSum(840)).toEqual([ + [40, 399, 401], + [56, 390, 394], + [105, 360, 375], + [120, 350, 370], + [140, 336, 364], + [168, 315, 357], + [210, 280, 350], + [240, 252, 348], + ]) }) - xit('can recognize a non pythagorean triplet', () => { - expect(new Triplet(5, 6, 7).isPythagorean()).toBe(false) + xtest('returns triplets with no factor smaller than minimum factor', () => { + expect(tripletsWithSum(90, { minFactor: 10 })).toEqual([[15, 36, 39]]) }) - xit('can make triplets up to 10', () => { - const triplets = Triplet.where({ maxFactor: 10 }) - const products = triplets - .sort() - .map((triplet: Triplet) => triplet.product()) - expect(products).toEqual([60, 480]) + xtest('returns triplets with no factor larger than maximum factor', () => { + expect(tripletsWithSum(840, { maxFactor: 349 })).toEqual([[240, 252, 348]]) }) - xit('can make triplets 11 through 20', () => { - const triplets = Triplet.where({ minFactor: 11, maxFactor: 20 }) - const products = triplets - .sort() - .map((triplet: Triplet) => triplet.product()) - expect(products).toEqual([3840]) + xtest('returns triplets with factors in range', () => { + expect(tripletsWithSum(840, { maxFactor: 352, minFactor: 150 })).toEqual([ + [210, 280, 350], + [240, 252, 348], + ]) }) - xit('can filter on sum', () => { - const triplets = Triplet.where({ sum: 180, maxFactor: 100 }) - const products = triplets - .sort() - .map((triplet: Triplet) => triplet.product()) - expect(products).toEqual([118080, 168480, 202500]) + test.skip('triplets for large number', () => { + expect(tripletsWithSum(30000)).toEqual([ + [1200, 14375, 14425], + [1875, 14000, 14125], + [5000, 12000, 13000], + [6000, 11250, 12750], + [7500, 10000, 12500], + ]) }) }) diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet.ts b/exercises/practice/pythagorean-triplet/pythagorean-triplet.ts index e69de29bb..c8be96ca3 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet.ts +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet.ts @@ -0,0 +1,19 @@ +type Options = { + minFactor?: number + maxFactor?: number + sum: number +} + +export function triplets({}: Options): Triplet[] { + throw new Error('Remove this statement and implement this function') +} + +class Triplet { + constructor() { + throw new Error('Remove this statement and implement this function') + } + + toArray(): [number, number, number] { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/queen-attack/.docs/instructions.append.md b/exercises/practice/queen-attack/.docs/instructions.append.md new file mode 100644 index 000000000..8a76348ff --- /dev/null +++ b/exercises/practice/queen-attack/.docs/instructions.append.md @@ -0,0 +1,19 @@ +# Instructions append + +A queen must be placed on a valid position on the board. +Two queens cannot share the same position. + +If a position has not been given, the queens are at their [default starting positions](https://en.wikipedia.org/wiki/Rules_of_chess#Initial_setup). That's the bottom row (1) for the white queen and the top row (8) for the black queen. Both queens start in the fourth column (d). + +```text + a b c d e f g h +8 _ _ _ B _ _ _ _ 8 +7 _ _ _ _ _ _ _ _ 7 +6 _ _ _ _ _ _ _ _ 6 +5 _ _ _ _ _ _ _ _ 5 +4 _ _ _ _ _ _ _ _ 4 +3 _ _ _ _ _ _ _ _ 3 +2 _ _ _ _ _ _ _ _ 2 +1 _ _ _ W _ _ _ _ 1 + a b c d e f g h +``` diff --git a/exercises/practice/queen-attack/.meta/proof.ci.ts b/exercises/practice/queen-attack/.meta/proof.ci.ts new file mode 100644 index 000000000..48293a90f --- /dev/null +++ b/exercises/practice/queen-attack/.meta/proof.ci.ts @@ -0,0 +1,83 @@ +const W = 8 +const H = 8 +const STARTING: Positions = { black: [0, 3], white: [7, 3] } as const + +type Position = readonly [number, number] + +type Positions = { + white: Position + black: Position +} + +function invalidPosition({ white, black }: Positions) { + if (white[0] < 0 || white[0] >= H || white[1] < 0 || white[1] >= W) { + return true + } + + if (black[0] < 0 || black[0] >= H || black[1] < 0 || black[1] >= W) { + return true + } + + return false +} + +function samePosition({ white, black }: Positions) { + return white[0] === black[0] && white[1] === black[1] +} + +function constructBoard() { + return new Array(W * H).fill('_') +} + +function placePieces(self: QueenAttack) { + const board = self.board + const [blackRow, blackColumn] = self.black + const [whiteRow, whiteColumn] = self.white + + board[blackRow * W + blackColumn] = 'B' + board[whiteRow * W + whiteColumn] = 'W' +} + +export class QueenAttack { + public readonly black: Position + public readonly white: Position + public readonly board: string[] + + constructor(params: Partial = {}) { + const fullParams = { ...STARTING, ...params } + if (invalidPosition(fullParams)) { + throw new Error('Queen must be placed on the board') + } + + if (samePosition(fullParams)) { + throw new Error('Queens cannot share the same space') + } + + this.black = fullParams.black + this.white = fullParams.white + this.board = constructBoard() + + placePieces(this) + + return this + } + + get canAttack(): boolean { + // Same row or column + if (this.black[0] === this.white[0] || this.black[1] === this.white[1]) { + return true + } + + // Diagonally + return ( + Math.abs(this.black[0] - this.white[0]) === + Math.abs(this.black[1] - this.white[1]) + ) + } + + toString(): string { + return Array.from({ length: H }, (_, row) => + this.board.slice(row * H, row * H + W).join(' ') + ).join('\n') + } +} diff --git a/exercises/practice/queen-attack/queen-attack.example.ts b/exercises/practice/queen-attack/queen-attack.example.ts deleted file mode 100644 index 9f07edaa7..000000000 --- a/exercises/practice/queen-attack/queen-attack.example.ts +++ /dev/null @@ -1,66 +0,0 @@ -class QueenAttack { - private readonly W = 8 - private readonly H = 8 - public black: number[] - public white: number[] - private board: string[] - - constructor(params: { black: [number, number]; white: [number, number] }) { - if (this.samePosition(params)) { - throw new Error('Queens cannot share the same space') - } - - this.black = params.black - this.white = params.white - this.board = this.constructBoard() - this.placePieces() - - this.toString = (): string => this.board.join('') - - return this - } - - private samePosition(positioning: { - white: number[] - black: number[] - }): boolean { - return ( - positioning.white[0] === positioning.black[0] && - positioning.white[1] === positioning.black[1] - ) - } - - private buildRow(cell: string, colCount: number): string[] { - return Array(...Array(colCount)).map(() => cell) - } - - private concatRows(row: string, rowCount: number): string[] { - return [ - ...Array.prototype.concat.apply(this.buildRow(row, rowCount)).join(''), - ] - } - - private constructBoard(): string[] { - let row = this.buildRow('_ ', this.W).join('') - row = `${row.substring(0, row.length - 1)}\n` - return this.concatRows(row, this.H) - } - - private placePieces(): void { - const board = this.board - board[this.black[0] * this.W * 2 + this.black[1] * 2] = 'B' - board[this.white[0] * this.W * 2 + this.white[1] * 2] = 'W' - } - - public canAttack = (): boolean => { - if (this.black[0] === this.white[0] || this.black[1] === this.white[1]) { - return true - } - return ( - Math.abs(this.black[0] - this.white[0]) === - Math.abs(this.black[1] - this.white[1]) - ) - } -} - -export default QueenAttack diff --git a/exercises/practice/queen-attack/queen-attack.test.ts b/exercises/practice/queen-attack/queen-attack.test.ts index 3591a7e15..14b21129b 100644 --- a/exercises/practice/queen-attack/queen-attack.test.ts +++ b/exercises/practice/queen-attack/queen-attack.test.ts @@ -1,77 +1,151 @@ -import QueenAttack from './queen-attack' +import { QueenAttack } from './queen-attack' describe('Queens', () => { - it('initialized with specific placement', () => { - const queens = new QueenAttack({ white: [3, 7], black: [6, 1] }) - expect(queens.white).toEqual([3, 7]) - expect(queens.black).toEqual([6, 1]) - }) + describe('Test creation of Queens with valid and invalid positions', () => { + test('queen with a valid position', () => { + const queens = new QueenAttack({ white: [2, 2] }) + expect(queens.white).toEqual([2, 2]) + }) - xit('cannot occupy the same space', () => { - const positioning: { black: [number, number]; white: [number, number] } = { - black: [2, 4], - white: [2, 4], - } - const expectedError = 'Queens cannot share the same space' - expect(() => new QueenAttack(positioning)).toThrow(expectedError) - }) + xtest('queen must have positive row', () => { + const positioning = { white: [-2, 2] } as const + const expectedError = 'Queen must be placed on the board' + expect(() => new QueenAttack(positioning)).toThrow(expectedError) + }) - xit('toString representation', () => { - const positioning: { black: [number, number]; white: [number, number] } = { - white: [2, 4], - black: [6, 6], - } - const queens = new QueenAttack(positioning) - const board = [ - '_ _ _ _ _ _ _ _', - '_ _ _ _ _ _ _ _', - '_ _ _ _ W _ _ _', - '_ _ _ _ _ _ _ _', - '_ _ _ _ _ _ _ _', - '_ _ _ _ _ _ _ _', - '_ _ _ _ _ _ B _', - '_ _ _ _ _ _ _ _\n', - ].join('\n') - expect(queens.toString()).toEqual(board) - }) + xtest('queen must have row on board', () => { + const positioning = { white: [8, 4] } as const + const expectedError = 'Queen must be placed on the board' + expect(() => new QueenAttack(positioning)).toThrow(expectedError) + }) - xit('queens cannot attack', () => { - const queens = new QueenAttack({ white: [2, 3], black: [4, 7] }) - expect(queens.canAttack()).toEqual(false) - }) + xtest('queen must have positive column', () => { + const positioning = { white: [2, -2] } as const + const expectedError = 'Queen must be placed on the board' + expect(() => new QueenAttack(positioning)).toThrow(expectedError) + }) - xit('queens can attack when they are on the same row', () => { - const queens = new QueenAttack({ white: [2, 4], black: [2, 7] }) - expect(queens.canAttack()).toEqual(true) - }) + xtest('queen must have column on board', () => { + const positioning = { white: [4, 8] } as const + const expectedError = 'Queen must be placed on the board' + expect(() => new QueenAttack(positioning)).toThrow(expectedError) + }) - xit('queens can attack when they are on the same column', () => { - const queens = new QueenAttack({ white: [5, 4], black: [2, 4] }) - expect(queens.canAttack()).toEqual(true) + xtest('two queens cannot occupy the same space', () => { + const positioning = { white: [2, 4], black: [2, 4] } as const + const expectedError = 'Queens cannot share the same space' + expect(() => new QueenAttack(positioning)).toThrow(expectedError) + }) }) - xit('queens can attack diagonally', () => { - const queens = new QueenAttack({ white: [1, 1], black: [6, 6] }) - expect(queens.canAttack()).toEqual(true) - }) + describe('Test the ability of one queen to attack another', () => { + xtest('queens cannot attack', () => { + const queens = new QueenAttack({ white: [2, 4], black: [6, 6] }) + expect(queens.canAttack).toEqual(false) + }) - xit('queens can attack another diagonally', () => { - const queens = new QueenAttack({ white: [0, 6], black: [1, 7] }) - expect(queens.canAttack()).toEqual(true) - }) + xtest('queens can attack when they are on the same row', () => { + const queens = new QueenAttack({ white: [2, 4], black: [2, 6] }) + expect(queens.canAttack).toEqual(true) + }) - xit('queens can attack yet another diagonally', () => { - const queens = new QueenAttack({ white: [4, 1], black: [6, 3] }) - expect(queens.canAttack()).toEqual(true) - }) + xtest('queens can attack when they are on the same column', () => { + const queens = new QueenAttack({ white: [4, 5], black: [2, 5] }) + expect(queens.canAttack).toEqual(true) + }) - xit('queens can attack on a north-east/south-west diagonal', () => { - const queens = new QueenAttack({ white: [7, 0], black: [0, 7] }) - expect(queens.canAttack()).toEqual(true) + xtest('queens can attack diagonally', () => { + const queens = new QueenAttack({ white: [2, 2], black: [0, 4] }) + expect(queens.canAttack).toEqual(true) + }) + + xtest('queens can attack another diagonally', () => { + const queens = new QueenAttack({ white: [2, 2], black: [3, 1] }) + expect(queens.canAttack).toEqual(true) + }) + + xtest('queens can attack yet another diagonally', () => { + const queens = new QueenAttack({ white: [2, 2], black: [1, 1] }) + expect(queens.canAttack).toEqual(true) + }) + + xtest('queens can attack diagonally, really', () => { + const queens = new QueenAttack({ white: [1, 7], black: [0, 6] }) + expect(queens.canAttack).toEqual(true) + }) + + xtest('queens can attack on a north-east/south-west diagonal', () => { + const queens = new QueenAttack({ white: [7, 0], black: [0, 7] }) + expect(queens.canAttack).toEqual(true) + }) + + xtest('queens can attack on another ne/sw diagonal', () => { + const queens = new QueenAttack({ white: [2, 6], black: [5, 3] }) + expect(queens.canAttack).toEqual(true) + }) }) - xit('queens can attack on another ne/sw diagonal', () => { - const queens = new QueenAttack({ white: [2, 6], black: [5, 3] }) - expect(queens.canAttack()).toEqual(true) + describe('Test the board visualisation', () => { + xtest('board', () => { + const positioning = { white: [3, 2], black: [6, 5] } as const + const queens = new QueenAttack(positioning) + const board = [ + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ W _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ B _ _', + '_ _ _ _ _ _ _ _', + ].join('\n') + expect(queens.toString()).toEqual(board) + }) + + xtest('board with queens at their starting positions', () => { + const queens = new QueenAttack() + const board = [ + '_ _ _ B _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ W _ _ _ _', + ].join('\n') + expect(queens.toString()).toEqual(board) + }) + + xtest('board with the black queen at her starting positions', () => { + const queens = new QueenAttack({ white: [1, 6] }) + const board = [ + '_ _ _ B _ _ _ _', + '_ _ _ _ _ _ W _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + ].join('\n') + expect(queens.toString()).toEqual(board) + }) + + xtest('board with queens at the edges', () => { + const positioning = { white: [0, 0], black: [7, 7] } as const + const queens = new QueenAttack(positioning) + const board = [ + 'W _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ _', + '_ _ _ _ _ _ _ B', + ].join('\n') + expect(queens.toString()).toEqual(board) + }) }) }) diff --git a/exercises/practice/queen-attack/queen-attack.ts b/exercises/practice/queen-attack/queen-attack.ts index e69de29bb..1d0419757 100644 --- a/exercises/practice/queen-attack/queen-attack.ts +++ b/exercises/practice/queen-attack/queen-attack.ts @@ -0,0 +1,24 @@ +type Position = readonly [number, number] + +type Positions = { + white: Position + black: Position +} +export class QueenAttack { + public readonly black: Position + public readonly white: Position + + // white: [whiteRow, whiteColumn] + // black: [blackRow, blackColumn] + constructor({}: Partial = {}) { + throw new Error('Remove this statement and implement this function') + } + + toString() { + throw new Error('Remove this statement and implement this function') + } + + get canAttack() { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/raindrops/.meta/proof.ci.ts b/exercises/practice/raindrops/.meta/proof.ci.ts new file mode 100644 index 000000000..82f4274c0 --- /dev/null +++ b/exercises/practice/raindrops/.meta/proof.ci.ts @@ -0,0 +1,15 @@ +export function convert(drops: number): string { + let converted = '' + + if (drops % 3 === 0) { + converted += 'Pling' + } + if (drops % 5 === 0) { + converted += 'Plang' + } + if (drops % 7 === 0) { + converted += 'Plong' + } + + return converted ? converted : drops.toString() +} diff --git a/exercises/practice/raindrops/raindrops.example.ts b/exercises/practice/raindrops/raindrops.example.ts deleted file mode 100644 index 2a526d58c..000000000 --- a/exercises/practice/raindrops/raindrops.example.ts +++ /dev/null @@ -1,17 +0,0 @@ -export default class Raindrops { - public convert(drops: number): string { - let converted = '' - - if (drops % 3 === 0) { - converted += 'Pling' - } - if (drops % 5 === 0) { - converted += 'Plang' - } - if (drops % 7 === 0) { - converted += 'Plong' - } - - return converted ? converted : drops.toString() - } -} diff --git a/exercises/practice/raindrops/raindrops.test.ts b/exercises/practice/raindrops/raindrops.test.ts index 04fe74b3c..5525d72c9 100644 --- a/exercises/practice/raindrops/raindrops.test.ts +++ b/exercises/practice/raindrops/raindrops.test.ts @@ -1,38 +1,35 @@ -import Raindrops from './raindrops' +import { convert } from './raindrops' describe('Raindrops', () => { - const drops: Raindrops = new Raindrops() + it('converts 1', () => expect(convert(1)).toEqual('1')) - it('converts 1', () => expect(drops.convert(1)).toEqual('1')) + xit('converts 3', () => expect(convert(3)).toEqual('Pling')) - xit('converts 3', () => expect(drops.convert(3)).toEqual('Pling')) + xit('converts 5', () => expect(convert(5)).toEqual('Plang')) - xit('converts 5', () => expect(drops.convert(5)).toEqual('Plang')) + xit('converts 7', () => expect(convert(7)).toEqual('Plong')) - xit('converts 7', () => expect(drops.convert(7)).toEqual('Plong')) + xit('converts 6', () => expect(convert(6)).toEqual('Pling')) - xit('converts 6', () => expect(drops.convert(6)).toEqual('Pling')) + xit('converts 9', () => expect(convert(9)).toEqual('Pling')) - xit('converts 9', () => expect(drops.convert(9)).toEqual('Pling')) + xit('converts 10', () => expect(convert(10)).toEqual('Plang')) - xit('converts 10', () => expect(drops.convert(10)).toEqual('Plang')) + xit('converts 14', () => expect(convert(14)).toEqual('Plong')) - xit('converts 14', () => expect(drops.convert(14)).toEqual('Plong')) + xit('converts 15', () => expect(convert(15)).toEqual('PlingPlang')) - xit('converts 15', () => expect(drops.convert(15)).toEqual('PlingPlang')) + xit('converts 21', () => expect(convert(21)).toEqual('PlingPlong')) - xit('converts 21', () => expect(drops.convert(21)).toEqual('PlingPlong')) + xit('converts 25', () => expect(convert(25)).toEqual('Plang')) - xit('converts 25', () => expect(drops.convert(25)).toEqual('Plang')) + xit('converts 35', () => expect(convert(35)).toEqual('PlangPlong')) - xit('converts 35', () => expect(drops.convert(35)).toEqual('PlangPlong')) + xit('converts 49', () => expect(convert(49)).toEqual('Plong')) - xit('converts 49', () => expect(drops.convert(49)).toEqual('Plong')) + xit('converts 52', () => expect(convert(52)).toEqual('52')) - xit('converts 52', () => expect(drops.convert(52)).toEqual('52')) + xit('converts 105', () => expect(convert(105)).toEqual('PlingPlangPlong')) - xit('converts 105', () => - expect(drops.convert(105)).toEqual('PlingPlangPlong')) - - xit('converts 12121', () => expect(drops.convert(12121)).toEqual('12121')) + xit('converts 12121', () => expect(convert(12121)).toEqual('12121')) }) diff --git a/exercises/practice/raindrops/raindrops.ts b/exercises/practice/raindrops/raindrops.ts index e69de29bb..d56119850 100644 --- a/exercises/practice/raindrops/raindrops.ts +++ b/exercises/practice/raindrops/raindrops.ts @@ -0,0 +1,3 @@ +export function convert() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/rational-numbers/rational-numbers.example.ts b/exercises/practice/rational-numbers/.meta/proof.ci.ts similarity index 98% rename from exercises/practice/rational-numbers/rational-numbers.example.ts rename to exercises/practice/rational-numbers/.meta/proof.ci.ts index 91a781328..421bc20fd 100644 --- a/exercises/practice/rational-numbers/rational-numbers.example.ts +++ b/exercises/practice/rational-numbers/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -class Rational { +export class Rational { private numerator: number private denominator: number @@ -90,5 +90,3 @@ class Rational { } } } - -export default Rational diff --git a/exercises/practice/rational-numbers/rational-numbers.test.ts b/exercises/practice/rational-numbers/rational-numbers.test.ts index 1ac0e5039..ebf88ac40 100644 --- a/exercises/practice/rational-numbers/rational-numbers.test.ts +++ b/exercises/practice/rational-numbers/rational-numbers.test.ts @@ -1,4 +1,4 @@ -import Rational from './rational-numbers' +import { Rational } from './rational-numbers' describe('Addition', () => { it('Add two positive rational numbers', () => { diff --git a/exercises/practice/rational-numbers/rational-numbers.ts b/exercises/practice/rational-numbers/rational-numbers.ts index e69de29bb..851d84fc7 100644 --- a/exercises/practice/rational-numbers/rational-numbers.ts +++ b/exercises/practice/rational-numbers/rational-numbers.ts @@ -0,0 +1,37 @@ +export class Rational { + constructor() { + throw new Error('Remove this statement and implement this function') + } + + add() { + throw new Error('Remove this statement and implement this function') + } + + sub() { + throw new Error('Remove this statement and implement this function') + } + + mul() { + throw new Error('Remove this statement and implement this function') + } + + div() { + throw new Error('Remove this statement and implement this function') + } + + abs() { + throw new Error('Remove this statement and implement this function') + } + + exprational() { + throw new Error('Remove this statement and implement this function') + } + + expreal() { + throw new Error('Remove this statement and implement this function') + } + + reduce() { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/react/react.example.ts b/exercises/practice/react/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/react/react.example.ts rename to exercises/practice/react/.meta/proof.ci.ts diff --git a/exercises/practice/rectangles/.meta/proof.ci.ts b/exercises/practice/rectangles/.meta/proof.ci.ts new file mode 100644 index 000000000..add3a5177 --- /dev/null +++ b/exercises/practice/rectangles/.meta/proof.ci.ts @@ -0,0 +1,58 @@ +export function count(diagram: string[]): number { + const rows = diagram.length + const cols = rows ? diagram[0].length : 0 + + let rectangles = 0 + + // All possible topleft corners + for (let y = 0; y < rows - 1; y++) { + for (let x = 0; x < cols - 1; x++) { + if (diagram[y].charAt(x) === '+') { + // All possible bottomright corners + for (let j = y + 1; j < rows; j++) { + for (let i = x + 1; i < cols; i++) { + // Check if all corners are valid + if ( + diagram[j].charAt(i) === '+' && + diagram[y].charAt(i) === '+' && + diagram[j].charAt(x) === '+' + ) { + let validRectangle = true + + // Check if all sides are valid + for (let s = x + 1; s < i; s++) { + if (!'+-'.includes(diagram[y].charAt(s))) { + validRectangle = false + } + } + + for (let s = x + 1; s < i; s++) { + if (!'+-'.includes(diagram[j].charAt(s))) { + validRectangle = false + } + } + + for (let t = y + 1; t < j; t++) { + if (!'+|'.includes(diagram[t].charAt(x))) { + validRectangle = false + } + } + + for (let t = y + 1; t < j; t++) { + if (!'+|'.includes(diagram[t].charAt(i))) { + validRectangle = false + } + } + + if (validRectangle) { + rectangles++ + } + } + } + } + } + } + } + + return rectangles +} diff --git a/exercises/practice/rectangles/rectangles.example.ts b/exercises/practice/rectangles/rectangles.example.ts deleted file mode 100644 index ccd5a7947..000000000 --- a/exercises/practice/rectangles/rectangles.example.ts +++ /dev/null @@ -1,62 +0,0 @@ -class Rectangles { - public static count(diagram: string[]): number { - const rows = diagram.length - const cols = rows ? diagram[0].length : 0 - - let rectangles = 0 - - // All possible topleft corners - for (let y = 0; y < rows - 1; y++) { - for (let x = 0; x < cols - 1; x++) { - if (diagram[y].charAt(x) === '+') { - // All possible bottomright corners - for (let j = y + 1; j < rows; j++) { - for (let i = x + 1; i < cols; i++) { - // Check if all corners are valid - if ( - diagram[j].charAt(i) === '+' && - diagram[y].charAt(i) === '+' && - diagram[j].charAt(x) === '+' - ) { - let validRectangle = true - - // Check if all sides are valid - for (let s = x + 1; s < i; s++) { - if (!'+-'.includes(diagram[y].charAt(s))) { - validRectangle = false - } - } - - for (let s = x + 1; s < i; s++) { - if (!'+-'.includes(diagram[j].charAt(s))) { - validRectangle = false - } - } - - for (let t = y + 1; t < j; t++) { - if (!'+|'.includes(diagram[t].charAt(x))) { - validRectangle = false - } - } - - for (let t = y + 1; t < j; t++) { - if (!'+|'.includes(diagram[t].charAt(i))) { - validRectangle = false - } - } - - if (validRectangle) { - rectangles++ - } - } - } - } - } - } - } - - return rectangles - } -} - -export default Rectangles diff --git a/exercises/practice/rectangles/rectangles.test.ts b/exercises/practice/rectangles/rectangles.test.ts index edaa9002f..8555d96b6 100644 --- a/exercises/practice/rectangles/rectangles.test.ts +++ b/exercises/practice/rectangles/rectangles.test.ts @@ -1,87 +1,69 @@ -import Rectangles from './rectangles' +import { count } from './rectangles' describe('Rectangles', () => { it('no rows', () => { const expected = 0 - const actual = Rectangles.count([]) + const actual = count([]) expect(actual).toEqual(expected) }) xit('no columns', () => { const expected = 0 - const actual = Rectangles.count(['']) + const actual = count(['']) expect(actual).toEqual(expected) }) xit('no rectangles', () => { const expected = 0 - const actual = Rectangles.count([' ']) + const actual = count([' ']) expect(actual).toEqual(expected) }) xit('one rectangle', () => { const expected = 1 - const actual = Rectangles.count(['+-+', '| |', '+-+']) + const actual = count(['+-+', '| |', '+-+']) expect(actual).toEqual(expected) }) xit('two rectangles without shared parts', () => { const expected = 2 - const actual = Rectangles.count([ - ' +-+', - ' | |', - '+-+-+', - '| | ', - '+-+ ', - ]) + const actual = count([' +-+', ' | |', '+-+-+', '| | ', '+-+ ']) expect(actual).toEqual(expected) }) xit('five rectangles with shared parts', () => { const expected = 5 - const actual = Rectangles.count([ - ' +-+', - ' | |', - '+-+-+', - '| | |', - '+-+-+', - ]) + const actual = count([' +-+', ' | |', '+-+-+', '| | |', '+-+-+']) expect(actual).toEqual(expected) }) xit('rectangle of height 1 is counted', () => { const expected = 1 - const actual = Rectangles.count(['+--+', '+--+']) + const actual = count(['+--+', '+--+']) expect(actual).toEqual(expected) }) xit('rectangle of width 1 is counted', () => { const expected = 1 - const actual = Rectangles.count(['++', '||', '++']) + const actual = count(['++', '||', '++']) expect(actual).toEqual(expected) }) xit('1x1 square is counted', () => { const expected = 1 - const actual = Rectangles.count(['++', '++']) + const actual = count(['++', '++']) expect(actual).toEqual(expected) }) xit('only complete rectangles are counted', () => { const expected = 1 - const actual = Rectangles.count([ - ' +-+', - ' |', - '+-+-+', - '| | -', - '+-+-+', - ]) + const actual = count([' +-+', ' |', '+-+-+', '| | -', '+-+-+']) expect(actual).toEqual(expected) }) xit('rectangles can be of different sizes', () => { const expected = 3 - const actual = Rectangles.count([ + const actual = count([ '+------+----+', '| | |', '+---+--+ |', @@ -93,7 +75,7 @@ describe('Rectangles', () => { xit('corner is required for a rectangle to be complete', () => { const expected = 2 - const actual = Rectangles.count([ + const actual = count([ '+------+----+', '| | |', '+------+ |', @@ -105,7 +87,7 @@ describe('Rectangles', () => { xit('large input with many rectangles', () => { const expected = 60 - const actual = Rectangles.count([ + const actual = count([ '+---+--+----+', '| +--+----+', '+---+--+ |', diff --git a/exercises/practice/rectangles/rectangles.ts b/exercises/practice/rectangles/rectangles.ts index e69de29bb..bc4826e25 100644 --- a/exercises/practice/rectangles/rectangles.ts +++ b/exercises/practice/rectangles/rectangles.ts @@ -0,0 +1,3 @@ +export function count() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/resistor-color-duo/.meta/proof.ci.ts b/exercises/practice/resistor-color-duo/.meta/proof.ci.ts new file mode 100644 index 000000000..c17219460 --- /dev/null +++ b/exercises/practice/resistor-color-duo/.meta/proof.ci.ts @@ -0,0 +1,19 @@ +// resistor-color solution START +const COLORS = [ + 'black', + 'brown', + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'violet', + 'grey', + 'white', +] + +const colorCode = (color: string) => COLORS.indexOf(color) +// resistor-color solution END + +export const decodedValue = ([tens, ones]: string[]) => + colorCode(tens) * 10 + colorCode(ones) diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo.example.ts b/exercises/practice/resistor-color-duo/resistor-color-duo.example.ts deleted file mode 100644 index 22fab2dfe..000000000 --- a/exercises/practice/resistor-color-duo/resistor-color-duo.example.ts +++ /dev/null @@ -1,40 +0,0 @@ -type colors = - | 'black' - | 'brown' - | 'red' - | 'orange' - | 'yellow' - | 'green' - | 'blue' - | 'violet' - | 'grey' - | 'white' - -const COLORS = [ - 'black', - 'brown', - 'red', - 'orange', - 'yellow', - 'green', - 'blue', - 'violet', - 'grey', - 'white', -] - -export class ResistorColor { - private tens: colors - private ones: colors - - constructor([tens, ones, ..._]: colors[]) { - if (tens === undefined || ones === undefined) { - throw new Error('At least two colors need to be present') - } - this.tens = tens - this.ones = ones - } - - public value = (): number => - COLORS.indexOf(this.tens) * 10 + COLORS.indexOf(this.ones) -} diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo.test.ts b/exercises/practice/resistor-color-duo/resistor-color-duo.test.ts index 1280c8d41..999ccbe64 100644 --- a/exercises/practice/resistor-color-duo/resistor-color-duo.test.ts +++ b/exercises/practice/resistor-color-duo/resistor-color-duo.test.ts @@ -1,34 +1,23 @@ -import { ResistorColor } from './resistor-color-duo' +import { decodedValue } from './resistor-color-duo' describe('Resistor Colors', () => { - it('Brown and black', () => { - const resistorColor = new ResistorColor(['brown', 'black']) - expect(resistorColor.value()).toEqual(10) + test('Brown and black', () => { + expect(decodedValue(['brown', 'black'])).toEqual(10) }) - xit('Blue and grey', () => { - const resistorColor = new ResistorColor(['blue', 'grey']) - expect(resistorColor.value()).toEqual(68) + xtest('Blue and grey', () => { + expect(decodedValue(['blue', 'grey'])).toEqual(68) }) - xit('Yellow and violet', () => { - const resistorColor = new ResistorColor(['yellow', 'violet']) - expect(resistorColor.value()).toEqual(47) + xtest('Yellow and violet', () => { + expect(decodedValue(['yellow', 'violet'])).toEqual(47) }) - xit('Orange and orange', () => { - const resistorColor = new ResistorColor(['orange', 'orange']) - expect(resistorColor.value()).toEqual(33) + xtest('Orange and orange', () => { + expect(decodedValue(['orange', 'orange'])).toEqual(33) }) - xit('Ignore additional colors', () => { - const resistorColor = new ResistorColor(['green', 'brown', 'orange']) - expect(resistorColor.value()).toEqual(51) - }) - - xit('Throws error when not enough colors', () => { - expect(() => new ResistorColor(['green'])).toThrowError( - 'At least two colors need to be present' - ) + xtest('Ignore additional colors', () => { + expect(decodedValue(['green', 'brown', 'orange'])).toEqual(51) }) }) diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo.ts b/exercises/practice/resistor-color-duo/resistor-color-duo.ts index 7eae8fdf9..616646c84 100644 --- a/exercises/practice/resistor-color-duo/resistor-color-duo.ts +++ b/exercises/practice/resistor-color-duo/resistor-color-duo.ts @@ -1,8 +1,3 @@ -export class ResistorColor { - private colors: string[] - - constructor(colors: string[]) { - this.colors = colors - } - value = (): number => 0 +export function decodedValue() { + throw new Error('Remove this statement and implement this function') } diff --git a/exercises/practice/resistor-color/resistor-color.example.ts b/exercises/practice/resistor-color/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/resistor-color/resistor-color.example.ts rename to exercises/practice/resistor-color/.meta/proof.ci.ts diff --git a/exercises/practice/reverse-string/.meta/proof.ci.ts b/exercises/practice/reverse-string/.meta/proof.ci.ts new file mode 100644 index 000000000..24ae361c7 --- /dev/null +++ b/exercises/practice/reverse-string/.meta/proof.ci.ts @@ -0,0 +1,3 @@ +export function reverse(value: string): string { + return value.split('').reverse().join('') +} diff --git a/exercises/practice/reverse-string/reverse-string.example.ts b/exercises/practice/reverse-string/reverse-string.example.ts deleted file mode 100644 index a47154a56..000000000 --- a/exercises/practice/reverse-string/reverse-string.example.ts +++ /dev/null @@ -1,7 +0,0 @@ -class ReverseString { - public static reverse(value: string): string { - return value.split('').reverse().join('') - } -} - -export default ReverseString diff --git a/exercises/practice/reverse-string/reverse-string.test.ts b/exercises/practice/reverse-string/reverse-string.test.ts index 857a484e3..507e8edbf 100644 --- a/exercises/practice/reverse-string/reverse-string.test.ts +++ b/exercises/practice/reverse-string/reverse-string.test.ts @@ -1,28 +1,28 @@ -import ReverseString from './reverse-string' +import { reverse } from './reverse-string' describe('Reverse String', () => { it('an empty string', () => { const expected = '' - expect(ReverseString.reverse('')).toEqual(expected) + expect(reverse('')).toEqual(expected) }) xit('a word', () => { const expected = 'tobor' - expect(ReverseString.reverse('robot')).toEqual(expected) + expect(reverse('robot')).toEqual(expected) }) xit('a capitalized word', () => { const expected = 'nemaR' - expect(ReverseString.reverse('Ramen')).toEqual(expected) + expect(reverse('Ramen')).toEqual(expected) }) xit('a sentence with punctuation', () => { const expected = `!yrgnuh m'I` - expect(ReverseString.reverse(`I'm hungry!`)).toEqual(expected) + expect(reverse(`I'm hungry!`)).toEqual(expected) }) xit('a palindrome', () => { const expected = 'racecar' - expect(ReverseString.reverse('racecar')).toEqual(expected) + expect(reverse('racecar')).toEqual(expected) }) }) diff --git a/exercises/practice/reverse-string/reverse-string.ts b/exercises/practice/reverse-string/reverse-string.ts index 123cd8cae..04784faa3 100644 --- a/exercises/practice/reverse-string/reverse-string.ts +++ b/exercises/practice/reverse-string/reverse-string.ts @@ -1,7 +1,3 @@ -class ReverseString { - static reverse(/* Parameters go here */) { - // Your code here - } +export function reverse(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') } - -export default ReverseString diff --git a/exercises/practice/rna-transcription/.meta/proof.ci.ts b/exercises/practice/rna-transcription/.meta/proof.ci.ts new file mode 100644 index 000000000..4a0492e78 --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/proof.ci.ts @@ -0,0 +1,17 @@ +export function toRna(input: string): string { + const dictionary: { [key: string]: string } = { + G: 'C', + C: 'G', + T: 'A', + A: 'U', + } + let temp = '' + input.split('').forEach((element) => { + const current = dictionary[element] + if (current === undefined) { + throw new Error('Invalid input DNA.') + } + temp += current + }) + return temp +} diff --git a/exercises/practice/rna-transcription/rna-transcription.example.ts b/exercises/practice/rna-transcription/rna-transcription.example.ts deleted file mode 100644 index 1fb72261d..000000000 --- a/exercises/practice/rna-transcription/rna-transcription.example.ts +++ /dev/null @@ -1,20 +0,0 @@ -class Transcriptor { - public toRna(input: string): string { - const dictionary: { [key: string]: string } = { - G: 'C', - C: 'G', - T: 'A', - A: 'U', - } - let temp = '' - input.split('').forEach((element) => { - const current = dictionary[element] - if (current === undefined) { - throw new Error('Invalid input DNA.') - } - temp += current - }) - return temp - } -} -export default Transcriptor diff --git a/exercises/practice/rna-transcription/rna-transcription.test.ts b/exercises/practice/rna-transcription/rna-transcription.test.ts index f84a73eb5..924945b25 100644 --- a/exercises/practice/rna-transcription/rna-transcription.test.ts +++ b/exercises/practice/rna-transcription/rna-transcription.test.ts @@ -1,39 +1,35 @@ -import Transcriptor from './rna-transcription' +import { toRna } from './rna-transcription' describe('Transcriptor', () => { - const transcriptor = new Transcriptor() - it('transcribes cytosine to guanine', () => { - expect(transcriptor.toRna('C')).toEqual('G') + expect(toRna('C')).toEqual('G') }) xit('transcribes guanine to cytosine', () => { - expect(transcriptor.toRna('G')).toEqual('C') + expect(toRna('G')).toEqual('C') }) xit('transcribes adenine to uracil', () => { - expect(transcriptor.toRna('A')).toEqual('U') + expect(toRna('A')).toEqual('U') }) xit('transcribes thymine to adenine', () => { - expect(transcriptor.toRna('T')).toEqual('A') + expect(toRna('T')).toEqual('A') }) xit('transcribes all dna nucleotides to their rna complements', () => { - expect(transcriptor.toRna('ACGTGGTCTTAA')).toEqual('UGCACCAGAAUU') + expect(toRna('ACGTGGTCTTAA')).toEqual('UGCACCAGAAUU') }) xit('correctly handles invalid input', () => { - expect(() => transcriptor.toRna('U')).toThrowError('Invalid input DNA.') + expect(() => toRna('U')).toThrowError('Invalid input DNA.') }) xit('correctly handles completely invalid input', () => { - expect(() => transcriptor.toRna('XXX')).toThrowError('Invalid input DNA.') + expect(() => toRna('XXX')).toThrowError('Invalid input DNA.') }) xit('correctly handles partially invalid input', () => { - expect(() => transcriptor.toRna('ACGTXXXCTTAA')).toThrowError( - 'Invalid input DNA.' - ) + expect(() => toRna('ACGTXXXCTTAA')).toThrowError('Invalid input DNA.') }) }) diff --git a/exercises/practice/rna-transcription/rna-transcription.ts b/exercises/practice/rna-transcription/rna-transcription.ts index d7681d4be..7b2c233ed 100644 --- a/exercises/practice/rna-transcription/rna-transcription.ts +++ b/exercises/practice/rna-transcription/rna-transcription.ts @@ -1,7 +1,3 @@ -class Transcriptor { - toRna(/* Parameters go here */) { - // Your code here - } +export function toRna() { + throw new Error('Remove this statement and implement this function') } - -export default Transcriptor diff --git a/exercises/practice/robot-name/robot-name.example.ts b/exercises/practice/robot-name/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/robot-name/robot-name.example.ts rename to exercises/practice/robot-name/.meta/proof.ci.ts diff --git a/exercises/practice/robot-simulator/.meta/proof.ci.ts b/exercises/practice/robot-simulator/.meta/proof.ci.ts new file mode 100644 index 000000000..e99da9b54 --- /dev/null +++ b/exercises/practice/robot-simulator/.meta/proof.ci.ts @@ -0,0 +1,89 @@ +export class InvalidInputError extends Error { + constructor(message: string) { + super() + this.message = message || 'Invalid Input' + } +} + +export class Robot { + coordinates: number[] + bearing: string + + static instructions(s: string) { + return [...s].map((character) => { + switch (character) { + case 'L': + return 'turnLeft' + case 'R': + return 'turnRight' + case 'A': + return 'advance' + default: + throw new InvalidInputError( + `${character} is not a valid instruction character.` + ) + } + }) + } + + constructor() { + this.coordinates = [0, 0] + this.bearing = 'north' + } + + set direction(next: string) { + const validDirections = ['north', 'south', 'east', 'west'] + if (!validDirections.includes(next)) { + throw new InvalidInputError('Invalid Robot Bearing') + } + + this.bearing = next + } + + advance() { + if (this.bearing === 'north') { + this.coordinates[1] += 1 + } else if (this.bearing === 'south') { + this.coordinates[1] -= 1 + } else if (this.bearing === 'east') { + this.coordinates[0] += 1 + } else if (this.bearing === 'west') { + this.coordinates[0] -= 1 + } + } + + turnLeft() { + if (this.bearing === 'north') { + this.direction = 'west' + } else if (this.bearing === 'south') { + this.direction = 'east' + } else if (this.bearing === 'east') { + this.direction = 'north' + } else if (this.bearing === 'west') { + this.direction = 'south' + } + } + + turnRight() { + if (this.bearing === 'north') { + this.direction = 'east' + } else if (this.bearing === 'south') { + this.direction = 'west' + } else if (this.bearing === 'east') { + this.direction = 'south' + } else if (this.bearing === 'west') { + this.direction = 'north' + } + } + + place(args: { x: number; y: number; direction: string }) { + this.coordinates = [args.x, args.y] + this.direction = args.direction + } + + evaluate(s: string) { + Robot.instructions(s).forEach((instruction) => { + this[instruction]() + }) + } +} diff --git a/exercises/practice/robot-simulator/robot-simulator.example.ts b/exercises/practice/robot-simulator/robot-simulator.example.ts deleted file mode 100644 index 50a021017..000000000 --- a/exercises/practice/robot-simulator/robot-simulator.example.ts +++ /dev/null @@ -1,78 +0,0 @@ -export default class Robot { - public coordinates: number[] - public bearing: string - - constructor( - xcoord: number = 0, - ycoord: number = 0, - direction: string = 'north' - ) { - this.coordinates = [xcoord, ycoord] - this.bearing = direction - } - - public at(xcoord: number, ycoord: number): void { - this.coordinates = [xcoord, ycoord] - } - - public orient(direction: string): string { - this.bearing = direction - return `The robot is pointed ${direction}` - } - - public advance(): void { - if (this.bearing === 'north') { - this.coordinates[1] += 1 - } else if (this.bearing === 'south') { - this.coordinates[1] -= 1 - } else if (this.bearing === 'east') { - this.coordinates[0] += 1 - } else if (this.bearing === 'west') { - this.coordinates[0] -= 1 - } - } - - public turnLeft(): void { - if (this.bearing === 'north') { - this.orient('west') - } else if (this.bearing === 'south') { - this.orient('east') - } else if (this.bearing === 'east') { - this.orient('north') - } else if (this.bearing === 'west') { - this.orient('south') - } - } - - public turnRight(): void { - if (this.bearing === 'north') { - this.orient('east') - } else if (this.bearing === 'south') { - this.orient('west') - } else if (this.bearing === 'east') { - this.orient('south') - } else if (this.bearing === 'west') { - this.orient('north') - } - } - - public instructions(s: string): Array<'turnLeft' | 'turnRight' | 'advance'> { - return [...s].map((character) => { - if (character === 'L') { - return 'turnLeft' - } else if (character === 'R') { - return 'turnRight' - } else if (character === 'A') { - return 'advance' - } else { - throw 'Invalid Instruction' - } - }) - } - - public evaluate(s: string): void { - this.instructions(s).forEach((instruction) => { - this[instruction]() - }) - } -} diff --git a/exercises/practice/robot-simulator/robot-simulator.test.ts b/exercises/practice/robot-simulator/robot-simulator.test.ts index dbf508983..94cf158d9 100644 --- a/exercises/practice/robot-simulator/robot-simulator.test.ts +++ b/exercises/practice/robot-simulator/robot-simulator.test.ts @@ -1,154 +1,236 @@ -import Robot from './robot-simulator' +import { Robot, InvalidInputError } from './robot-simulator' + +function turnRight(robot: Robot) { + robot.evaluate('R') +} + +function turnLeft(robot: Robot) { + robot.evaluate('L') +} + +function advance(robot: Robot) { + robot.evaluate('A') +} describe('Robot', () => { - const robot = new Robot() + describe('Create robot', () => { + test('facing north by default', () => { + const robot = new Robot() + expect(robot.bearing).toEqual('north') + }) - it('robot bearing', () => { - const directions = ['east', 'west', 'north', 'south'] + test('facing east', () => { + const robot = new Robot() + robot.place({ direction: 'east', x: 0, y: 0 }) - directions.forEach((currentDirection) => { - robot.orient(currentDirection) - expect(robot.bearing).toEqual(currentDirection) + expect(robot.bearing).toEqual('east') }) - }) - xit('invalid robot bearing', () => { - try { - robot.orient('crood') - } catch (exception) { - expect(exception).toEqual('Invalid Robot Bearing') - } - }) + test('facing west, at origin', () => { + const robot = new Robot() + robot.place({ direction: 'west', x: 0, y: 0 }) - xit('turn right from north', () => { - robot.orient('north') - robot.turnRight() - expect(robot.bearing).toEqual('east') - }) + expect(robot.bearing).toEqual('west') + expect(robot.coordinates).toEqual([0, 0]) + }) - xit('turn right from east', () => { - robot.orient('east') - robot.turnRight() - expect(robot.bearing).toEqual('south') - }) + test('at negative position facing south', () => { + const robot = new Robot() + robot.place({ direction: 'south', x: -1, y: -1 }) - xit('turn right from south', () => { - robot.orient('south') - robot.turnRight() - expect(robot.bearing).toEqual('west') - }) + expect(robot.bearing).toEqual('south') + expect(robot.coordinates).toEqual([-1, -1]) + }) - xit('turn right from west', () => { - robot.orient('west') - robot.turnRight() - expect(robot.bearing).toEqual('north') - }) + xtest('invalid robot bearing', () => { + const robot = new Robot() - xit('turn left from north', () => { - robot.orient('north') - robot.turnLeft() - expect(robot.bearing).toEqual('west') + expect(InvalidInputError.prototype).toBeInstanceOf(Error) + expect(() => robot.place({ direction: 'crood', x: 0, y: 0 })).toThrow( + InvalidInputError + ) + }) }) - xit('turn left from east', () => { - robot.orient('east') - robot.turnLeft() - expect(robot.bearing).toEqual('north') - }) + describe('Rotating clockwise', () => { + const robot = new Robot() - xit('turn left from south', () => { - robot.orient('south') - robot.turnLeft() - expect(robot.bearing).toEqual('east') - }) + xtest('changes north to east', () => { + robot.place({ direction: 'north', x: 0, y: 0 }) - xit('turn left from west', () => { - robot.orient('west') - robot.turnLeft() - expect(robot.bearing).toEqual('south') - }) + turnRight(robot) - xit('robot coordinates', () => { - robot.at(3, 0) - expect(robot.coordinates).toEqual([3, 0]) - }) + expect(robot.bearing).toEqual('east') + expect(robot.coordinates).toEqual([0, 0]) + }) - xit('other robot coordinates', () => { - robot.at(-2, 5) - expect(robot.coordinates).toEqual([-2, 5]) - }) + xtest('changes east to south', () => { + robot.place({ direction: 'east', x: 0, y: 0 }) - xit('advance when facing north', () => { - robot.at(0, 0) - robot.orient('north') - robot.advance() - expect(robot.coordinates).toEqual([0, 1]) - }) + turnRight(robot) - xit('advance when facing east', () => { - robot.at(0, 0) - robot.orient('east') - robot.advance() - expect(robot.coordinates).toEqual([1, 0]) - }) + expect(robot.bearing).toEqual('south') + expect(robot.coordinates).toEqual([0, 0]) + }) - xit('advance when facing south', () => { - robot.at(0, 0) - robot.orient('south') - robot.advance() - expect(robot.coordinates).toEqual([0, -1]) - }) + xtest('changes south to west', () => { + robot.place({ direction: 'south', x: 0, y: 0 }) - xit('advance when facing west', () => { - robot.at(0, 0) - robot.orient('west') - robot.advance() - expect(robot.coordinates).toEqual([-1, 0]) - }) + turnRight(robot) - xit('instructions for turning left', () => { - expect(robot.instructions('L')).toEqual(['turnLeft']) - }) + expect(robot.bearing).toEqual('west') + expect(robot.coordinates).toEqual([0, 0]) + }) - xit('instructions for turning right', () => { - expect(robot.instructions('R')).toEqual(['turnRight']) - }) + xtest('changes west to north', () => { + robot.place({ direction: 'west', x: 0, y: 0 }) + + turnRight(robot) - xit('instructions for advancing', () => { - expect(robot.instructions('A')).toEqual(['advance']) + expect(robot.bearing).toEqual('north') + expect(robot.coordinates).toEqual([0, 0]) + }) }) - xit('series of instructions', () => { - expect(robot.instructions('RAAL')).toEqual([ - 'turnRight', - 'advance', - 'advance', - 'turnLeft', - ]) + describe('Rotating counter-clockwise', () => { + const robot = new Robot() + + xtest('changes north to west', () => { + robot.place({ direction: 'north', x: 0, y: 0 }) + + turnLeft(robot) + + expect(robot.bearing).toEqual('west') + expect(robot.coordinates).toEqual([0, 0]) + }) + + xtest('changes west to south', () => { + robot.place({ direction: 'west', x: 0, y: 0 }) + + turnLeft(robot) + + expect(robot.bearing).toEqual('south') + expect(robot.coordinates).toEqual([0, 0]) + }) + + xtest('changes south to east', () => { + robot.place({ direction: 'south', x: 0, y: 0 }) + + turnLeft(robot) + + expect(robot.bearing).toEqual('east') + expect(robot.coordinates).toEqual([0, 0]) + }) + + xtest('changes east to north', () => { + robot.place({ direction: 'east', x: 0, y: 0 }) + + turnLeft(robot) + + expect(robot.bearing).toEqual('north') + expect(robot.coordinates).toEqual([0, 0]) + }) }) - xit('instruct robot', () => { - const robotI = new Robot(-2, 1, 'east') - robotI.evaluate('RLAALAL') - expect(robotI.coordinates).toEqual([0, 2]) - expect(robotI.bearing).toEqual('west') + describe('Moving forward one', () => { + const robot = new Robot() + + xtest('advance when facing north', () => { + robot.place({ direction: 'north', x: 0, y: 0 }) + + advance(robot) + + expect(robot.coordinates).toEqual([0, 1]) + expect(robot.bearing).toEqual('north') + }) + + xtest('advance when facing south', () => { + robot.place({ direction: 'south', x: 0, y: 0 }) + + advance(robot) + + expect(robot.coordinates).toEqual([0, -1]) + expect(robot.bearing).toEqual('south') + }) + + xtest('advance when facing east', () => { + robot.place({ direction: 'east', x: 0, y: 0 }) + + advance(robot) + + expect(robot.coordinates).toEqual([1, 0]) + expect(robot.bearing).toEqual('east') + }) + + xtest('advance when facing west', () => { + robot.place({ direction: 'west', x: 0, y: 0 }) + + advance(robot) + + expect(robot.coordinates).toEqual([-1, 0]) + expect(robot.bearing).toEqual('west') + }) }) - xit('instruct many robots', () => { - const robot1 = new Robot(0, 0, 'north') - const robot2 = new Robot(2, -7, 'east') - const robot3 = new Robot(8, 4, 'south') - robot1.evaluate('LAAARALA') - robot2.evaluate('RRAAAAALA') - robot3.evaluate('LAAARRRALLLL') + describe('Follow series of instructions', () => { + const robot = new Robot() + + xtest('moving east and north from README', () => { + robot.place({ x: 7, y: 3, direction: 'north' }) + + robot.evaluate('RAALAL') + + expect(robot.coordinates).toEqual([9, 4]) + expect(robot.bearing).toEqual('west') + }) + + xtest('moving west and north', () => { + robot.place({ x: 0, y: 0, direction: 'north' }) + + robot.evaluate('LAAARALA') + + expect(robot.coordinates).toEqual([-4, 1]) + expect(robot.bearing).toEqual('west') + }) + + xtest('moving west and south', () => { + robot.place({ x: 2, y: -7, direction: 'east' }) - expect(robot1.coordinates).toEqual([-4, 1]) - expect(robot1.bearing).toEqual('west') + robot.evaluate('RRAAAAALA') - expect(robot2.coordinates).toEqual([-3, -8]) - expect(robot2.bearing).toEqual('south') + expect(robot.coordinates).toEqual([-3, -8]) + expect(robot.bearing).toEqual('south') + }) + + xtest('moving east and north', () => { + robot.place({ x: 8, y: 4, direction: 'south' }) + + robot.evaluate('LAAARRRALLLL') + + expect(robot.coordinates).toEqual([11, 5]) + expect(robot.bearing).toEqual('north') + }) - expect(robot3.coordinates).toEqual([11, 5]) - expect(robot3.bearing).toEqual('north') + xtest('instruct many robots', () => { + const robot1 = new Robot() + const robot2 = new Robot() + const robot3 = new Robot() + robot1.place({ x: 0, y: 0, direction: 'north' }) + robot2.place({ x: 2, y: -7, direction: 'east' }) + robot3.place({ x: 8, y: 4, direction: 'south' }) + + robot1.evaluate('LAAARALA') + robot2.evaluate('RRAAAAALA') + robot3.evaluate('LAAARRRALLLL') + + expect(robot1.coordinates).toEqual([-4, 1]) + expect(robot1.bearing).toEqual('west') + + expect(robot2.coordinates).toEqual([-3, -8]) + expect(robot2.bearing).toEqual('south') + + expect(robot3.coordinates).toEqual([11, 5]) + expect(robot3.bearing).toEqual('north') + }) }) }) diff --git a/exercises/practice/robot-simulator/robot-simulator.ts b/exercises/practice/robot-simulator/robot-simulator.ts index e69de29bb..3cc97dc3b 100644 --- a/exercises/practice/robot-simulator/robot-simulator.ts +++ b/exercises/practice/robot-simulator/robot-simulator.ts @@ -0,0 +1,26 @@ +export class InvalidInputError extends Error { + constructor(message: string) { + super() + this.message = message || 'Invalid Input' + } +} + +type Direction = 'north' | 'east' | 'south' | 'west' + +export class Robot { + get bearing() { + throw new Error('Remove this statement and implement this function') + } + + get coordinates() { + throw new Error('Remove this statement and implement this function') + } + + place({}: { x: number; y: number; direction: Direction }) { + throw new Error('Remove this statement and implement this function') + } + + evaluate(instructions: string) { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/roman-numerals/roman-numerals.example.ts b/exercises/practice/roman-numerals/.meta/proof.ci.ts similarity index 53% rename from exercises/practice/roman-numerals/roman-numerals.example.ts rename to exercises/practice/roman-numerals/.meta/proof.ci.ts index 894112b31..6c97b2dd2 100644 --- a/exercises/practice/roman-numerals/roman-numerals.example.ts +++ b/exercises/practice/roman-numerals/.meta/proof.ci.ts @@ -1,5 +1,8 @@ -class RomanNumerals { - private static readonly arabicToRomanConversions = [ +export function toRoman(number: number): string { + let result = '' + let remainingNumber = number + + const mappings = [ { arabic: 1000, roman: 'M' }, { arabic: 900, roman: 'CM' }, { arabic: 500, roman: 'D' }, @@ -15,18 +18,12 @@ class RomanNumerals { { arabic: 1, roman: 'I' }, ] - public static roman(n: number): string { - let result = '' - - this.arabicToRomanConversions.forEach((conversion) => { - while (n >= conversion.arabic) { - result += conversion.roman - n -= conversion.arabic - } - }) + mappings.forEach((mapping) => { + while (remainingNumber >= mapping.arabic) { + result += mapping.roman + remainingNumber -= mapping.arabic + } + }) - return result - } + return result } - -export default RomanNumerals diff --git a/exercises/practice/roman-numerals/roman-numerals.test.ts b/exercises/practice/roman-numerals/roman-numerals.test.ts index 85123deb9..ffb9e560a 100644 --- a/exercises/practice/roman-numerals/roman-numerals.test.ts +++ b/exercises/practice/roman-numerals/roman-numerals.test.ts @@ -1,98 +1,23 @@ -import RomanNumerals from './roman-numerals' - -describe('RomanNumerals', () => { - it('1 is a single I', () => { - const expected = 'I' - expect(RomanNumerals.roman(1)).toEqual(expected) - }) - - xit("2 is two I's", () => { - const expected = 'II' - expect(RomanNumerals.roman(2)).toEqual(expected) - }) - - xit("3 is three I's", () => { - const expected = 'III' - expect(RomanNumerals.roman(3)).toEqual(expected) - }) - - xit('4, being 5 - 1, is IV', () => { - const expected = 'IV' - expect(RomanNumerals.roman(4)).toEqual(expected) - }) - - xit('5 is a single V', () => { - const expected = 'V' - expect(RomanNumerals.roman(5)).toEqual(expected) - }) - - xit('6, being 5 + 1, is VI', () => { - const expected = 'VI' - expect(RomanNumerals.roman(6)).toEqual(expected) - }) - - xit('9, being 10 - 1, is IX', () => { - const expected = 'IX' - expect(RomanNumerals.roman(9)).toEqual(expected) - }) - - xit("20 is two X's", () => { - const expected = 'XXVII' - expect(RomanNumerals.roman(27)).toEqual(expected) - }) - - xit('48 is not 50 - 2 but rather 40 + 8', () => { - const expected = 'XLVIII' - expect(RomanNumerals.roman(48)).toEqual(expected) - }) - - xit('49 is not 40 + 5 + 4 but rather 50 - 10 + 10 - 1', () => { - const expected = 'XLIX' - expect(RomanNumerals.roman(49)).toEqual(expected) - }) - - xit('50 is a single L', () => { - const expected = 'LIX' - expect(RomanNumerals.roman(59)).toEqual(expected) - }) - - xit('90, being 100 - 10, is XC', () => { - const expected = 'XCIII' - expect(RomanNumerals.roman(93)).toEqual(expected) - }) - - xit('100 is a single C', () => { - const expected = 'CXLI' - expect(RomanNumerals.roman(141)).toEqual(expected) - }) - - xit('60, being 50 + 10, is LX', () => { - const expected = 'CLXIII' - expect(RomanNumerals.roman(163)).toEqual(expected) - }) - - xit('400, being 500 - 100, is CD', () => { - const expected = 'CDII' - expect(RomanNumerals.roman(402)).toEqual(expected) - }) - - xit('500 is a single D', () => { - const expected = 'DLXXV' - expect(RomanNumerals.roman(575)).toEqual(expected) - }) - - xit('900, being 1000 - 100, is CM', () => { - const expected = 'CMXI' - expect(RomanNumerals.roman(911)).toEqual(expected) - }) - - xit('1000 is a single M', () => { - const expected = 'MXXIV' - expect(RomanNumerals.roman(1024)).toEqual(expected) - }) - - xit("3000 is three M's", () => { - const expected = 'MMM' - expect(RomanNumerals.roman(3000)).toEqual(expected) - }) +import { toRoman } from './roman-numerals' + +describe('toRoman()', () => { + test('converts 1', () => expect(toRoman(1)).toEqual('I')) + xtest('converts 2', () => expect(toRoman(2)).toEqual('II')) + xtest('converts 3', () => expect(toRoman(3)).toEqual('III')) + xtest('converts 4', () => expect(toRoman(4)).toEqual('IV')) + xtest('converts 5', () => expect(toRoman(5)).toEqual('V')) + xtest('converts 6', () => expect(toRoman(6)).toEqual('VI')) + xtest('converts 9', () => expect(toRoman(9)).toEqual('IX')) + xtest('converts 27', () => expect(toRoman(27)).toEqual('XXVII')) + xtest('converts 48', () => expect(toRoman(48)).toEqual('XLVIII')) + xtest('converts 49', () => expect(toRoman(49)).toEqual('XLIX')) + xtest('converts 59', () => expect(toRoman(59)).toEqual('LIX')) + xtest('converts 93', () => expect(toRoman(93)).toEqual('XCIII')) + xtest('converts 141', () => expect(toRoman(141)).toEqual('CXLI')) + xtest('converts 163', () => expect(toRoman(163)).toEqual('CLXIII')) + xtest('converts 402', () => expect(toRoman(402)).toEqual('CDII')) + xtest('converts 575', () => expect(toRoman(575)).toEqual('DLXXV')) + xtest('converts 911', () => expect(toRoman(911)).toEqual('CMXI')) + xtest('converts 1024', () => expect(toRoman(1024)).toEqual('MXXIV')) + xtest('converts 3000', () => expect(toRoman(3000)).toEqual('MMM')) }) diff --git a/exercises/practice/roman-numerals/roman-numerals.ts b/exercises/practice/roman-numerals/roman-numerals.ts index 3665041ac..a8bc6bab3 100644 --- a/exercises/practice/roman-numerals/roman-numerals.ts +++ b/exercises/practice/roman-numerals/roman-numerals.ts @@ -1,7 +1,3 @@ -class RomanNumerals { - static roman(/* Parameters go here */) { - // Your code here - } +export const toRoman = () => { + throw new Error('Remove this statement and implement this function') } - -export default RomanNumerals diff --git a/exercises/practice/rotational-cipher/.meta/proof.ci.ts b/exercises/practice/rotational-cipher/.meta/proof.ci.ts new file mode 100644 index 000000000..0e38d1738 --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/proof.ci.ts @@ -0,0 +1,17 @@ +export function rotate(text: string, shiftBy: number): string { + return [...text] + .map((c) => { + const isUpper = c.toUpperCase() === c + const isAlpha = c.match(/[a-z]/i) + const caseCharCode = (isUpper ? 'A' : 'a').charCodeAt(0) + if (isAlpha) { + const charCode = c.charCodeAt(0) + return String.fromCharCode( + ((charCode - caseCharCode + shiftBy) % 26) + caseCharCode + ) + } else { + return c + } + }) + .join('') +} diff --git a/exercises/practice/rotational-cipher/rotational-cipher.example.ts b/exercises/practice/rotational-cipher/rotational-cipher.example.ts deleted file mode 100644 index 31a35c53a..000000000 --- a/exercises/practice/rotational-cipher/rotational-cipher.example.ts +++ /dev/null @@ -1,19 +0,0 @@ -export default class RotationalCipher { - public static rotate(text: string, shiftBy: number): string { - return [...text] - .map((c) => { - const isUpper = c.toUpperCase() === c - const isAlpha = c.match(/[a-z]/i) - const caseCharCode = (isUpper ? 'A' : 'a').charCodeAt(0) - if (isAlpha) { - const charCode = c.charCodeAt(0) - return String.fromCharCode( - ((charCode - caseCharCode + shiftBy) % 26) + caseCharCode - ) - } else { - return c - } - }) - .join('') - } -} diff --git a/exercises/practice/rotational-cipher/rotational-cipher.test.ts b/exercises/practice/rotational-cipher/rotational-cipher.test.ts index ddae747fd..4c778f9e5 100644 --- a/exercises/practice/rotational-cipher/rotational-cipher.test.ts +++ b/exercises/practice/rotational-cipher/rotational-cipher.test.ts @@ -1,56 +1,53 @@ -import RotationalCipher from './rotational-cipher' +import { rotate } from './rotational-cipher' describe('RotationalCipher', () => { it('rotate a by 1', () => { - const result = RotationalCipher.rotate('a', 1) + const result = rotate('a', 1) expect(result).toEqual('b') }) xit('rotate a by 26, same output as input', () => { - const result = RotationalCipher.rotate('a', 26) + const result = rotate('a', 26) expect(result).toEqual('a') }) xit('rotate a by 0, same output as input', () => { - const result = RotationalCipher.rotate('a', 0) + const result = rotate('a', 0) expect(result).toEqual('a') }) xit('rotate m by 13', () => { - const result = RotationalCipher.rotate('m', 13) + const result = rotate('m', 13) expect(result).toEqual('z') }) xit('rotate n by 13 with wrap around alphabet', () => { - const result = RotationalCipher.rotate('n', 13) + const result = rotate('n', 13) expect(result).toEqual('a') }) xit('rotate capital letters', () => { - const result = RotationalCipher.rotate('OMG', 5) + const result = rotate('OMG', 5) expect(result).toEqual('TRL') }) xit('rotate spaces', () => { - const result = RotationalCipher.rotate('O M G', 5) + const result = rotate('O M G', 5) expect(result).toEqual('T R L') }) xit('rotate numbers', () => { - const result = RotationalCipher.rotate('Testing 1 2 3 testing', 4) + const result = rotate('Testing 1 2 3 testing', 4) expect(result).toEqual('Xiwxmrk 1 2 3 xiwxmrk') }) xit('rotate punctuation', () => { - const result = RotationalCipher.rotate("Let's eat, Grandma!", 21) + const result = rotate("Let's eat, Grandma!", 21) expect(result).toEqual("Gzo'n zvo, Bmviyhv!") }) xit('rotate all letters', () => { - const result = RotationalCipher.rotate( - 'The quick brown fox jumps over the lazy dog.', - 13 - ) + const result = rotate('The quick brown fox jumps over the lazy dog.', 13) expect(result).toEqual('Gur dhvpx oebja sbk whzcf bire gur ynml qbt.') }) }) diff --git a/exercises/practice/rotational-cipher/rotational-cipher.ts b/exercises/practice/rotational-cipher/rotational-cipher.ts index c8e470571..0341a3a34 100644 --- a/exercises/practice/rotational-cipher/rotational-cipher.ts +++ b/exercises/practice/rotational-cipher/rotational-cipher.ts @@ -1 +1,3 @@ -export default class RotationalCipher {} +export function rotate() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/run-length-encoding/.meta/proof.ci.ts b/exercises/practice/run-length-encoding/.meta/proof.ci.ts new file mode 100644 index 000000000..42b35b2e2 --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/proof.ci.ts @@ -0,0 +1,11 @@ +export function encode(plaintext: string): string { + return plaintext.replace(/([\w\s])\1*/g, (match) => { + return match.length > 1 ? match.length + match[0] : match[0] + }) +} + +export function decode(cypher: string): string { + return cypher.replace(/(\d+)(\w|\s)/g, (_match, repeats, char) => { + return new Array(+repeats + 1).join(char) + }) +} diff --git a/exercises/practice/run-length-encoding/run-length-encoding.example.ts b/exercises/practice/run-length-encoding/run-length-encoding.example.ts deleted file mode 100644 index e2f56f361..000000000 --- a/exercises/practice/run-length-encoding/run-length-encoding.example.ts +++ /dev/null @@ -1,15 +0,0 @@ -class RunLengthEncoding { - public static encode(plaintext: string): string { - return plaintext.replace(/([\w\s])\1*/g, (match) => { - return match.length > 1 ? match.length + match[0] : match[0] - }) - } - - public static decode(cypher: string): string { - return cypher.replace(/(\d+)(\w|\s)/g, (_match, repeats, char) => { - return new Array(+repeats + 1).join(char) - }) - } -} - -export default RunLengthEncoding diff --git a/exercises/practice/run-length-encoding/run-length-encoding.test.ts b/exercises/practice/run-length-encoding/run-length-encoding.test.ts index 0ebaa6448..0868014f2 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding.test.ts +++ b/exercises/practice/run-length-encoding/run-length-encoding.test.ts @@ -1,77 +1,73 @@ -import RunLengthEncoding from './run-length-encoding' +import { encode, decode } from './run-length-encoding' describe('run-length encode a string', () => { it('empty string', () => { const expected = '' - expect(RunLengthEncoding.encode('')).toEqual(expected) + expect(encode('')).toEqual(expected) }) it('single characters only are encoded without count', () => { const expected = 'XYZ' - expect(RunLengthEncoding.encode('XYZ')).toEqual(expected) + expect(encode('XYZ')).toEqual(expected) }) it('string with no single characters', () => { const expected = '2A3B4C' - expect(RunLengthEncoding.encode('AABBBCCCC')).toEqual(expected) + expect(encode('AABBBCCCC')).toEqual(expected) }) it('single characters mixed with repeated characters', () => { const expected = '12WB12W3B24WB' expect( - RunLengthEncoding.encode( - 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB' - ) + encode('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB') ).toEqual(expected) }) it('multiple whitespace mixed in string', () => { const expected = '2 hs2q q2w2 ' - expect(RunLengthEncoding.encode(' hsqq qww ')).toEqual(expected) + expect(encode(' hsqq qww ')).toEqual(expected) }) it('lowercase characters', () => { const expected = '2a3b4c' - expect(RunLengthEncoding.encode('aabbbcccc')).toEqual(expected) + expect(encode('aabbbcccc')).toEqual(expected) }) }) describe('run-length decode a string', () => { it('empty string', () => { const expected = '' - expect(RunLengthEncoding.decode('')).toEqual(expected) + expect(decode('')).toEqual(expected) }) it('single characters only', () => { const expected = 'XYZ' - expect(RunLengthEncoding.decode('XYZ')).toEqual(expected) + expect(decode('XYZ')).toEqual(expected) }) it('string with no single characters', () => { const expected = 'AABBBCCCC' - expect(RunLengthEncoding.decode('2A3B4C')).toEqual(expected) + expect(decode('2A3B4C')).toEqual(expected) }) it('single characters with repeated characters', () => { const expected = 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB' - expect(RunLengthEncoding.decode('12WB12W3B24WB')).toEqual(expected) + expect(decode('12WB12W3B24WB')).toEqual(expected) }) it('multiple whitespace mixed in string', () => { const expected = ' hsqq qww ' - expect(RunLengthEncoding.decode('2 hs2q q2w2 ')).toEqual(expected) + expect(decode('2 hs2q q2w2 ')).toEqual(expected) }) it('lower case string', () => { const expected = 'aabbbcccc' - expect(RunLengthEncoding.decode('2a3b4c')).toEqual(expected) + expect(decode('2a3b4c')).toEqual(expected) }) }) describe('encode and then decode', () => { it('encode followed by decode gives original string', () => { - expect( - RunLengthEncoding.decode(RunLengthEncoding.encode('zzz ZZ zZ')) - ).toEqual('zzz ZZ zZ') + expect(decode(encode('zzz ZZ zZ'))).toEqual('zzz ZZ zZ') }) }) diff --git a/exercises/practice/run-length-encoding/run-length-encoding.ts b/exercises/practice/run-length-encoding/run-length-encoding.ts index e69de29bb..0d193e0fe 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding.ts +++ b/exercises/practice/run-length-encoding/run-length-encoding.ts @@ -0,0 +1,7 @@ +export function encode() { + throw new Error('Remove this statement and implement this function') +} + +export function decode() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/saddle-points/.meta/proof.ci.ts b/exercises/practice/saddle-points/.meta/proof.ci.ts new file mode 100644 index 000000000..188e88f2e --- /dev/null +++ b/exercises/practice/saddle-points/.meta/proof.ci.ts @@ -0,0 +1,33 @@ +export function saddlePoints( + matrix: number[][] +): Array<{ row: number; column: number }> { + const maximumRowValues: number[] = [] + const minimumColumnValues: number[] = [] + + for (let i = 0; i <= matrix.length - 1; i++) { + let maximumRowValue = Number.MIN_VALUE + for (let j = 0; j < matrix[0].length; j++) { + maximumRowValue = Math.max(maximumRowValue, matrix[i][j]) + } + maximumRowValues.push(maximumRowValue) + } + + for (let i = 0; i <= matrix[0].length - 1; i++) { + let minimumColumnValue = Number.MAX_VALUE + for (let j = 0; j <= matrix.length - 1; j++) { + minimumColumnValue = Math.min(minimumColumnValue, matrix[j][i]) + } + minimumColumnValues.push(minimumColumnValue) + } + + const resultPoints: Array<{ row: number; column: number }> = [] + for (let i = 0; i < maximumRowValues.length; i++) { + for (let j = 0; j < minimumColumnValues.length; j++) { + if (maximumRowValues[i] === minimumColumnValues[j]) { + resultPoints.push({ row: i + 1, column: j + 1 }) + } + } + } + + return resultPoints +} diff --git a/exercises/practice/saddle-points/saddle-points.example.ts b/exercises/practice/saddle-points/saddle-points.example.ts deleted file mode 100644 index f41ccfe26..000000000 --- a/exercises/practice/saddle-points/saddle-points.example.ts +++ /dev/null @@ -1,37 +0,0 @@ -class SaddlePoints { - public static saddlePoints( - matrix: number[][] - ): Array<{ row: number; column: number }> { - const maximumRowValues: number[] = [] - const minimumColumnValues: number[] = [] - - for (let i = 0; i <= matrix.length - 1; i++) { - let maximumRowValue = Number.MIN_VALUE - for (let j = 0; j < matrix[0].length; j++) { - maximumRowValue = Math.max(maximumRowValue, matrix[i][j]) - } - maximumRowValues.push(maximumRowValue) - } - - for (let i = 0; i <= matrix[0].length - 1; i++) { - let minimumColumnValue = Number.MAX_VALUE - for (let j = 0; j <= matrix.length - 1; j++) { - minimumColumnValue = Math.min(minimumColumnValue, matrix[j][i]) - } - minimumColumnValues.push(minimumColumnValue) - } - - const resultPoints: Array<{ row: number; column: number }> = [] - for (let i = 0; i < maximumRowValues.length; i++) { - for (let j = 0; j < minimumColumnValues.length; j++) { - if (maximumRowValues[i] === minimumColumnValues[j]) { - resultPoints.push({ row: i + 1, column: j + 1 }) - } - } - } - - return resultPoints - } -} - -export default SaddlePoints diff --git a/exercises/practice/saddle-points/saddle-points.test.ts b/exercises/practice/saddle-points/saddle-points.test.ts index b21418fc5..1eb8b530b 100644 --- a/exercises/practice/saddle-points/saddle-points.test.ts +++ b/exercises/practice/saddle-points/saddle-points.test.ts @@ -1,10 +1,10 @@ -import SaddlePoints from './saddle-points' +import { saddlePoints } from './saddle-points' describe('Saddle Points', () => { it('Can identify single saddle point', () => { const expected = [{ row: 2, column: 1 }] expect( - SaddlePoints.saddlePoints([ + saddlePoints([ [9, 8, 7], [5, 3, 2], [6, 6, 7], @@ -14,13 +14,13 @@ describe('Saddle Points', () => { xit('Can identify that empty matrix has no saddle points', () => { const expected: number[] = [] - expect(SaddlePoints.saddlePoints([[]])).toEqual(expected) + expect(saddlePoints([[]])).toEqual(expected) }) xit('Can identify lack of saddle points when there are none', () => { const expected: number[] = [] expect( - SaddlePoints.saddlePoints([ + saddlePoints([ [1, 2, 3], [3, 1, 2], [2, 3, 1], @@ -35,7 +35,7 @@ describe('Saddle Points', () => { { row: 3, column: 2 }, ] expect( - SaddlePoints.saddlePoints([ + saddlePoints([ [4, 5, 4], [3, 5, 5], [1, 5, 4], @@ -50,7 +50,7 @@ describe('Saddle Points', () => { { row: 2, column: 3 }, ] expect( - SaddlePoints.saddlePoints([ + saddlePoints([ [6, 7, 8], [5, 5, 5], [7, 5, 6], @@ -61,7 +61,7 @@ describe('Saddle Points', () => { xit('Can identify saddle point in bottom right corner', () => { const expected = [{ row: 3, column: 3 }] expect( - SaddlePoints.saddlePoints([ + saddlePoints([ [8, 7, 9], [6, 7, 6], [3, 2, 5], @@ -75,7 +75,7 @@ describe('Saddle Points', () => { { row: 1, column: 3 }, ] expect( - SaddlePoints.saddlePoints([ + saddlePoints([ [3, 1, 3], [3, 2, 4], ]) @@ -87,7 +87,7 @@ describe('Saddle Points', () => { { row: 2, column: 1 }, { row: 4, column: 1 }, ] - expect(SaddlePoints.saddlePoints([[2], [1], [4], [1]])).toEqual(expected) + expect(saddlePoints([[2], [1], [4], [1]])).toEqual(expected) }) xit('Can identify that saddle points in a single row matrix are those with the maximum value', () => { @@ -95,6 +95,6 @@ describe('Saddle Points', () => { { row: 1, column: 2 }, { row: 1, column: 4 }, ] - expect(SaddlePoints.saddlePoints([[2, 5, 3, 5]])).toEqual(expected) + expect(saddlePoints([[2, 5, 3, 5]])).toEqual(expected) }) }) diff --git a/exercises/practice/saddle-points/saddle-points.ts b/exercises/practice/saddle-points/saddle-points.ts index f2cf7da5f..3116c54fb 100644 --- a/exercises/practice/saddle-points/saddle-points.ts +++ b/exercises/practice/saddle-points/saddle-points.ts @@ -1,7 +1,3 @@ -class SaddlePoints { - static saddlePoints(/* Parameters go here */) { - // Your code here - } +export function saddlePoints(/* Parameters go here */) { + throw new Error('Remove this statement and implement this function') } - -export default SaddlePoints diff --git a/exercises/practice/say/say.example.ts b/exercises/practice/say/.meta/proof.ci.ts similarity index 89% rename from exercises/practice/say/say.example.ts rename to exercises/practice/say/.meta/proof.ci.ts index 3fb6d50b8..235175df6 100644 --- a/exercises/practice/say/say.example.ts +++ b/exercises/practice/say/.meta/proof.ci.ts @@ -88,14 +88,10 @@ function zeroTo99(input: number): string { return '' } -class Say { - public inEnglish(input: number): string { - if (input < 0 || input > 999999999999) { - throw new Error('Number must be between 0 and 999,999,999,999.') - } - - return numberGenerator(input) +export function sayInEnglish(input: number): string { + if (input < 0 || input > 999999999999) { + throw new Error('Number must be between 0 and 999,999,999,999.') } -} -export default Say + return numberGenerator(input) +} diff --git a/exercises/practice/say/say.test.ts b/exercises/practice/say/say.test.ts index 12a10aef6..d530845f7 100644 --- a/exercises/practice/say/say.test.ts +++ b/exercises/practice/say/say.test.ts @@ -1,59 +1,58 @@ -import Say from './say' +import { sayInEnglish } from './say' describe('say', () => { - const say = new Say() it('zero', () => { - expect(say.inEnglish(0)).toBe('zero') + expect(sayInEnglish(0)).toBe('zero') }) xit('one', () => { - expect(say.inEnglish(1)).toBe('one') + expect(sayInEnglish(1)).toBe('one') }) xit('fourteen', () => { - expect(say.inEnglish(14)).toBe('fourteen') + expect(sayInEnglish(14)).toBe('fourteen') }) xit('twenty', () => { - expect(say.inEnglish(20)).toBe('twenty') + expect(sayInEnglish(20)).toBe('twenty') }) xit('twenty-two', () => { - expect(say.inEnglish(22)).toBe('twenty-two') + expect(sayInEnglish(22)).toBe('twenty-two') }) xit('one hundred', () => { - expect(say.inEnglish(100)).toBe('one hundred') + expect(sayInEnglish(100)).toBe('one hundred') }) xit('one hundred twenty-three', () => { - expect(say.inEnglish(123)).toBe('one hundred twenty-three') + expect(sayInEnglish(123)).toBe('one hundred twenty-three') }) xit('one thousand', () => { - expect(say.inEnglish(1000)).toBe('one thousand') + expect(sayInEnglish(1000)).toBe('one thousand') }) xit('one thousand two hundred thirty-four', () => { - expect(say.inEnglish(1234)).toBe('one thousand two hundred thirty-four') + expect(sayInEnglish(1234)).toBe('one thousand two hundred thirty-four') }) xit('one million', () => { - expect(say.inEnglish(1000000)).toBe('one million') + expect(sayInEnglish(1000000)).toBe('one million') }) xit('one million two', () => { - expect(say.inEnglish(1000002)).toBe('one million two') + expect(sayInEnglish(1000002)).toBe('one million two') }) xit('one million two thousand three hundred forty-five', () => { - expect(say.inEnglish(1002345)).toBe( + expect(sayInEnglish(1002345)).toBe( 'one million two thousand three hundred forty-five' ) }) xit('one billion', () => { - expect(say.inEnglish(1000000000)).toBe('one billion') + expect(sayInEnglish(1000000000)).toBe('one billion') }) xit('a really big number', () => { @@ -61,18 +60,18 @@ describe('say', () => { expected += 'six hundred fifty-four million ' expected += 'three hundred twenty-one thousand ' expected += 'one hundred twenty-three' - expect(say.inEnglish(987654321123)).toBe(expected) + expect(sayInEnglish(987654321123)).toBe(expected) }) xit('raises an error below zero', () => { expect(() => { - say.inEnglish(-1) + sayInEnglish(-1) }).toThrowError('Number must be between 0 and 999,999,999,999.') }) xit('raises an error above 999,999,999,999', () => { expect(() => { - say.inEnglish(1000000000000) + sayInEnglish(1000000000000) }).toThrowError('Number must be between 0 and 999,999,999,999.') }) }) diff --git a/exercises/practice/say/say.ts b/exercises/practice/say/say.ts index e69de29bb..6043075eb 100644 --- a/exercises/practice/say/say.ts +++ b/exercises/practice/say/say.ts @@ -0,0 +1,3 @@ +export function sayInEnglish() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/scrabble-score/scrabble-score.example.ts b/exercises/practice/scrabble-score/.meta/proof.ci.ts similarity index 86% rename from exercises/practice/scrabble-score/scrabble-score.example.ts rename to exercises/practice/scrabble-score/.meta/proof.ci.ts index c690c441b..46cae25fd 100644 --- a/exercises/practice/scrabble-score/scrabble-score.example.ts +++ b/exercises/practice/scrabble-score/.meta/proof.ci.ts @@ -31,7 +31,7 @@ const letterScores: LetterScore = { z: 10, } -const score = (word: string | undefined): number => { +export const score = (word: string | undefined): number => { word = word ? word.toLowerCase() : '' let output = 0 @@ -41,5 +41,3 @@ const score = (word: string | undefined): number => { return output } - -export default score diff --git a/exercises/practice/scrabble-score/scrabble-score.test.ts b/exercises/practice/scrabble-score/scrabble-score.test.ts index 36efc9af6..e944b1da6 100644 --- a/exercises/practice/scrabble-score/scrabble-score.test.ts +++ b/exercises/practice/scrabble-score/scrabble-score.test.ts @@ -1,4 +1,4 @@ -import score from './scrabble-score' +import { score } from './scrabble-score' describe('Scrabble', () => { it('scores an empty word as zero', () => expect(score('')).toEqual(0)) diff --git a/exercises/practice/scrabble-score/scrabble-score.ts b/exercises/practice/scrabble-score/scrabble-score.ts index e69de29bb..8be6c9508 100644 --- a/exercises/practice/scrabble-score/scrabble-score.ts +++ b/exercises/practice/scrabble-score/scrabble-score.ts @@ -0,0 +1,3 @@ +export function score() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/secret-handshake/.meta/proof.ci.ts b/exercises/practice/secret-handshake/.meta/proof.ci.ts new file mode 100644 index 000000000..3689fd9cb --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/proof.ci.ts @@ -0,0 +1,15 @@ +const handshakeCommands = ['wink', 'double blink', 'close your eyes', 'jump'] + +export const commands = (handshake: string) => { + if (typeof handshake !== 'number') { + throw new Error('Handshake must be a number') + } + + const shakeWith = handshakeCommands.filter( + (_, i) => handshake & Math.pow(2, i) + ) + + if (handshake & Math.pow(2, 4)) shakeWith.reverse() + + return shakeWith +} diff --git a/exercises/practice/secret-handshake/secret-handshake.example.ts b/exercises/practice/secret-handshake/secret-handshake.example.ts deleted file mode 100644 index 9a9daeaf5..000000000 --- a/exercises/practice/secret-handshake/secret-handshake.example.ts +++ /dev/null @@ -1,42 +0,0 @@ -class HandShake { - private readonly allCommands = [ - 'wink', - 'double blink', - 'close your eyes', - 'jump', - 'REVERSE', - ] as const - private readonly shakeWith: string[] - - constructor(handshake: number) { - if (typeof handshake !== 'number') { - throw new Error('Handshake must be a number') - } - - this.shakeWith = this.calculateHandshake(handshake) - } - - public commands(): string[] { - return this.shakeWith - } - - private calculateHandshake(handshake: number): string[] { - const shakeWith = [] - - for (let i = 0; i < this.allCommands.length; i++) { - const currentCommand = this.allCommands[i] - const handshakeHasCommand = handshake & Math.pow(2, i) - - if (handshakeHasCommand) { - if (currentCommand === 'REVERSE') { - shakeWith.reverse() - } else { - shakeWith.push(this.allCommands[i]) - } - } - } - return shakeWith - } -} - -export default HandShake diff --git a/exercises/practice/secret-handshake/secret-handshake.test.ts b/exercises/practice/secret-handshake/secret-handshake.test.ts index b059b3bc1..3b2da3d2a 100644 --- a/exercises/practice/secret-handshake/secret-handshake.test.ts +++ b/exercises/practice/secret-handshake/secret-handshake.test.ts @@ -1,69 +1,59 @@ -import HandShake from './secret-handshake' - -describe('Create a handshake for a number', () => { - it('wink for 1', () => { - const handshake = new HandShake(1) - const expected = ['wink'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('double blink for 10', () => { - const handshake = new HandShake(2) - const expected = ['double blink'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('close your eyes for 100', () => { - const handshake = new HandShake(4) - const expected = ['close your eyes'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('jump for 1000', () => { - const handshake = new HandShake(8) - const expected = ['jump'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('combine two actions', () => { - const handshake = new HandShake(3) - const expected = ['wink', 'double blink'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('reverse two actions', () => { - const handshake = new HandShake(19) - const expected = ['double blink', 'wink'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('reversing one action gives the same action', () => { - const handshake = new HandShake(24) - const expected = ['jump'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('reversing no actions still gives no actions', () => { - const handshake = new HandShake(16) - const expected: string[] = [] - expect(handshake.commands()).toEqual(expected) - }) - - xit('all possible actions', () => { - const handshake = new HandShake(15) - const expected = ['wink', 'double blink', 'close your eyes', 'jump'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('reverse all possible actions', () => { - const handshake = new HandShake(31) - const expected = ['jump', 'close your eyes', 'double blink', 'wink'] - expect(handshake.commands()).toEqual(expected) - }) - - xit('do nothing for zero', () => { - const handshake = new HandShake(0) - const expected: string[] = [] - expect(handshake.commands()).toEqual(expected) +import { commands } from './secret-handshake' + +describe('Secret Handshake', () => { + describe('Create A Handshake For A Number', () => { + test('wink for 1', () => { + expect(commands(1)).toEqual(['wink']) + }) + + xtest('double blink for 10', () => { + expect(commands(2)).toEqual(['double blink']) + }) + + xtest('close your eyes for 100', () => { + expect(commands(4)).toEqual(['close your eyes']) + }) + + xtest('jump for 1000', () => { + expect(commands(8)).toEqual(['jump']) + }) + + xtest('combine two actions', () => { + expect(commands(3)).toEqual(['wink', 'double blink']) + }) + + xtest('reverse two actions', () => { + expect(commands(19)).toEqual(['double blink', 'wink']) + }) + + xtest('reversing one action gives the same action', () => { + expect(commands(24)).toEqual(['jump']) + }) + + xtest('reversing no actions still gives no actions', () => { + expect(commands(16)).toEqual([]) + }) + + xtest('all possible actions', () => { + expect(commands(15)).toEqual([ + 'wink', + 'double blink', + 'close your eyes', + 'jump', + ]) + }) + + xtest('reverse all possible actions', () => { + expect(commands(31)).toEqual([ + 'jump', + 'close your eyes', + 'double blink', + 'wink', + ]) + }) + + xtest('do nothing for zero', () => { + expect(commands(0)).toEqual([]) + }) }) }) diff --git a/exercises/practice/secret-handshake/secret-handshake.ts b/exercises/practice/secret-handshake/secret-handshake.ts index e69de29bb..6abd766aa 100644 --- a/exercises/practice/secret-handshake/secret-handshake.ts +++ b/exercises/practice/secret-handshake/secret-handshake.ts @@ -0,0 +1,3 @@ +export function commands() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/series/.meta/proof.ci.ts b/exercises/practice/series/.meta/proof.ci.ts new file mode 100644 index 000000000..77b532eb6 --- /dev/null +++ b/exercises/practice/series/.meta/proof.ci.ts @@ -0,0 +1,44 @@ +export class Series { + numberString: string + digits: number[] + + constructor(numberString: string) { + if (!numberString) { + throw new Error('series cannot be empty') + } + + this.numberString = numberString + this.digits = this.getDigits() + } + + getDigits(): number[] { + return [...this.numberString].map((digit) => parseInt(digit, 10)) + } + + slices(sliceSize: number): number[][] { + const result: number[][] = [] + let slice: number[] = [] + + if (sliceSize < 0) { + throw new Error('slice length cannot be negative') + } + + if (sliceSize == 0) { + throw new Error('slice length cannot be zero') + } + + if (sliceSize > this.digits.length) { + throw new Error('slice length cannot be greater than series length') + } + + for (let i = 0; i < this.digits.length - sliceSize + 1; i += 1) { + for (let j = 0; j < sliceSize; j += 1) { + slice.push(this.digits[i + j]) + } + result.push(slice) + slice = [] + } + + return result + } +} diff --git a/exercises/practice/series/series.example.ts b/exercises/practice/series/series.example.ts deleted file mode 100644 index 665ccba6f..000000000 --- a/exercises/practice/series/series.example.ts +++ /dev/null @@ -1,27 +0,0 @@ -export default class Series { - private readonly numberString: string - public readonly digits: number[] - - constructor(numberString: string) { - this.numberString = numberString - this.digits = this.getDigits() - } - - private getDigits(): number[] { - return [...this.numberString].map((digit) => parseInt(digit, 10)) - } - - public slices(sliceSize: number): number[][] { - const result: number[][] = [] - - if (sliceSize > this.digits.length) { - throw new Error('Slice size is too big.') - } - - for (let i = 0; i < this.digits.length - sliceSize + 1; i++) { - result.push(this.digits.slice(i, i + sliceSize)) - } - - return result - } -} diff --git a/exercises/practice/series/series.test.ts b/exercises/practice/series/series.test.ts index addf4751f..ec858e872 100644 --- a/exercises/practice/series/series.test.ts +++ b/exercises/practice/series/series.test.ts @@ -1,90 +1,69 @@ -import Series from './series' +import { Series } from './series' describe('Series', () => { - it('has digits (short)', () => { - expect(new Series('01234').digits).toEqual([0, 1, 2, 3, 4]) + xtest('slices of one from one', () => { + expect(new Series('1').slices(1)).toEqual([[1]]) }) - xit('has digits (long)', () => { - expect(new Series('0123456789').digits).toEqual([ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - ]) + xtest('slices of one from two', () => { + expect(new Series('12').slices(1)).toEqual([[1], [2]]) }) - xit('keeps the digit order if reversed', () => { - expect(new Series('9876543210').digits).toEqual([ - 9, - 8, - 7, - 6, - 5, - 4, - 3, - 2, - 1, - 0, - ]) + xtest('slices of two', () => { + expect(new Series('35').slices(2)).toEqual([[3, 5]]) }) - xit('keeps arbitrary digit order', () => { - expect(new Series('936923468').digits).toEqual([9, 3, 6, 9, 2, 3, 4, 6, 8]) - }) - - xit('can slice by 1', () => { - expect(new Series('01234').slices(1)).toEqual([[0], [1], [2], [3], [4]]) + xtest('slices of two overlap', () => { + expect(new Series('9142').slices(2)).toEqual([ + [9, 1], + [1, 4], + [4, 2], + ]) }) - xit('can slice by 2', () => { - expect(new Series('98273463').slices(2)).toEqual([ - [9, 8], - [8, 2], - [2, 7], - [7, 3], - [3, 4], - [4, 6], - [6, 3], + xtest('slices can include duplicates', () => { + expect(new Series('777777').slices(3)).toEqual([ + [7, 7, 7], + [7, 7, 7], + [7, 7, 7], + [7, 7, 7], ]) }) - xit('can slice by 3', () => { - expect(new Series('01234').slices(3)).toEqual([ - [0, 1, 2], - [1, 2, 3], - [2, 3, 4], + xtest('slices of long series', () => { + expect(new Series('918493904243').slices(5)).toEqual([ + [9, 1, 8, 4, 9], + [1, 8, 4, 9, 3], + [8, 4, 9, 3, 9], + [4, 9, 3, 9, 0], + [9, 3, 9, 0, 4], + [3, 9, 0, 4, 2], + [9, 0, 4, 2, 4], + [0, 4, 2, 4, 3], ]) }) - xit('can slice by 3 with duplicate digits', () => { - expect(new Series('31001').slices(3)).toEqual([ - [3, 1, 0], - [1, 0, 0], - [0, 0, 1], - ]) + xtest('slice length is too large', () => { + expect(() => { + new Series('12345').slices(6) + }).toThrow(new Error('slice length cannot be greater than series length')) }) - xit('can slice by 4', () => { - expect(new Series('91274').slices(4)).toEqual([ - [9, 1, 2, 7], - [1, 2, 7, 4], - ]) + xtest('slice length cannot be zero', () => { + expect(() => { + new Series('12345').slices(0) + }).toThrow(new Error('slice length cannot be zero')) }) - xit('can slice by 5', () => { - expect(new Series('81228').slices(5)).toEqual([[8, 1, 2, 2, 8]]) + xtest('slice length cannot be negative', () => { + expect(() => { + new Series('123').slices(-1) + }).toThrow(new Error('slice length cannot be negative')) }) - xit('throws an error if not enough digits to slice', () => { + xtest('empty series is invalid', () => { expect(() => { - new Series('01032987583').slices(12) - }).toThrow() + new Series('').slices(1) + }).toThrow(new Error('series cannot be empty')) }) }) diff --git a/exercises/practice/series/series.ts b/exercises/practice/series/series.ts index e69de29bb..a4d1337b3 100644 --- a/exercises/practice/series/series.ts +++ b/exercises/practice/series/series.ts @@ -0,0 +1,9 @@ +export class Series { + constructor(series: unknown) { + throw new Error('Remove this statement and implement this function') + } + + slices(sliceLength: unknown): unknown { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/sieve/.meta/proof.ci.ts b/exercises/practice/sieve/.meta/proof.ci.ts new file mode 100644 index 000000000..960f18802 --- /dev/null +++ b/exercises/practice/sieve/.meta/proof.ci.ts @@ -0,0 +1,33 @@ +export function primes(limit: number): number[] { + if (limit === 2) { + return [limit] + } + + const sieve: boolean[] = [] + for (let i = 0; i < limit; i++) { + sieve[i] = true + } + + const primes: number[] = [] + const maxCandidate = Math.floor(Math.sqrt(limit)) + for (let candidate = 2; candidate <= maxCandidate + 1; candidate++) { + if (!sieve[candidate - 1]) { + continue + } + + primes.push(candidate) + let multiple = candidate * candidate + while (multiple <= limit) { + sieve[multiple - 1] = false + multiple += candidate + } + } + + for (let i = maxCandidate + 1; i <= limit; i++) { + if (sieve[i - 1]) { + primes.push(i) + } + } + + return primes +} diff --git a/exercises/practice/sieve/sieve.example.ts b/exercises/practice/sieve/sieve.example.ts deleted file mode 100644 index 30e51b6b9..000000000 --- a/exercises/practice/sieve/sieve.example.ts +++ /dev/null @@ -1,37 +0,0 @@ -class Sieve { - public static primes(limit: number): number[] { - if (limit === 2) { - return [limit] - } - - const sieve: boolean[] = [] - for (let i = 0; i < limit; i++) { - sieve[i] = true - } - - const primes: number[] = [] - const maxCandidate = Math.floor(Math.sqrt(limit)) - for (let candidate = 2; candidate <= maxCandidate + 1; candidate++) { - if (!sieve[candidate - 1]) { - continue - } - - primes.push(candidate) - let multiple = candidate * candidate - while (multiple <= limit) { - sieve[multiple - 1] = false - multiple += candidate - } - } - - for (let i = maxCandidate + 1; i <= limit; i++) { - if (sieve[i - 1]) { - primes.push(i) - } - } - - return primes - } -} - -export default Sieve diff --git a/exercises/practice/sieve/sieve.test.ts b/exercises/practice/sieve/sieve.test.ts index ed1e9c4e1..4e89063df 100644 --- a/exercises/practice/sieve/sieve.test.ts +++ b/exercises/practice/sieve/sieve.test.ts @@ -1,24 +1,24 @@ -import Sieve from './sieve' +import { primes } from './sieve' describe('Sieve', () => { it('no primes under two', () => { const expected: number[] = [] - expect(Sieve.primes(1)).toEqual(expected) + expect(primes(1)).toEqual(expected) }) xit('find first prime', () => { const expected = [2] - expect(Sieve.primes(2)).toEqual(expected) + expect(primes(2)).toEqual(expected) }) xit('find primes up to 10', () => { const expected = [2, 3, 5, 7] - expect(Sieve.primes(10)).toEqual(expected) + expect(primes(10)).toEqual(expected) }) xit('limit is prime', () => { const expected = [2, 3, 5, 7, 11, 13] - expect(Sieve.primes(13)).toEqual(expected) + expect(primes(13)).toEqual(expected) }) xit('find primes up to 1000', () => { @@ -192,6 +192,6 @@ describe('Sieve', () => { 991, 997, ] - expect(Sieve.primes(1000)).toEqual(expected) + expect(primes(1000)).toEqual(expected) }) }) diff --git a/exercises/practice/sieve/sieve.ts b/exercises/practice/sieve/sieve.ts index e69de29bb..364afc35a 100644 --- a/exercises/practice/sieve/sieve.ts +++ b/exercises/practice/sieve/sieve.ts @@ -0,0 +1,3 @@ +export function primes() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/simple-cipher/simple-cipher.example.ts b/exercises/practice/simple-cipher/.meta/proof.ci.ts similarity index 96% rename from exercises/practice/simple-cipher/simple-cipher.example.ts rename to exercises/practice/simple-cipher/.meta/proof.ci.ts index 3c647bd28..2cd548687 100644 --- a/exercises/practice/simple-cipher/simple-cipher.example.ts +++ b/exercises/practice/simple-cipher/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -class SimpleCipher { +export class SimpleCipher { public key: string constructor(key?: string) { @@ -45,5 +45,3 @@ class SimpleCipher { return decodedMessage } } - -export default SimpleCipher diff --git a/exercises/practice/simple-cipher/simple-cipher.test.ts b/exercises/practice/simple-cipher/simple-cipher.test.ts index e6aadced4..010c14411 100644 --- a/exercises/practice/simple-cipher/simple-cipher.test.ts +++ b/exercises/practice/simple-cipher/simple-cipher.test.ts @@ -1,4 +1,4 @@ -import SimpleCipher from './simple-cipher' +import { SimpleCipher } from './simple-cipher' describe('Random key generation', () => { xit('generates keys at random', () => { diff --git a/exercises/practice/simple-cipher/simple-cipher.ts b/exercises/practice/simple-cipher/simple-cipher.ts index a464a166d..cd3bec7ed 100644 --- a/exercises/practice/simple-cipher/simple-cipher.ts +++ b/exercises/practice/simple-cipher/simple-cipher.ts @@ -1,11 +1,9 @@ -class SimpleCipher { +export class SimpleCipher { encode(/* Parameters go here */) { - // Your code here + throw new Error('Remove this statement and implement this function') } decode(/* Parameters go here */) { - // Your code here + throw new Error('Remove this statement and implement this function') } } - -export default SimpleCipher diff --git a/exercises/practice/space-age/.meta/proof.ci.ts b/exercises/practice/space-age/.meta/proof.ci.ts new file mode 100644 index 000000000..a8dd01378 --- /dev/null +++ b/exercises/practice/space-age/.meta/proof.ci.ts @@ -0,0 +1,20 @@ +const EARTH_TO_OTHER_PLANETS = { + mercury: 0.2408467, + venus: 0.61519726, + earth: 1, + mars: 1.8808158, + jupiter: 11.862615, + saturn: 29.447498, + uranus: 84.016846, + neptune: 164.79132, +} + +export const age = ( + planet: keyof typeof EARTH_TO_OTHER_PLANETS, + seconds: number +) => { + const earthYears = seconds / 31557600 + const years = earthYears / EARTH_TO_OTHER_PLANETS[planet] + + return Number(years.toFixed(2)) +} diff --git a/exercises/practice/space-age/space-age.example.ts b/exercises/practice/space-age/space-age.example.ts deleted file mode 100644 index ae9315276..000000000 --- a/exercises/practice/space-age/space-age.example.ts +++ /dev/null @@ -1,36 +0,0 @@ -export default class SpaceAge { - public readonly seconds: number = 0 - - constructor(input: number) { - this.seconds = input - } - - private rounder(planetNumber: number): number { - return Math.round((this.seconds / planetNumber) * 100) / 100 - } - - public onMercury(): number { - return this.rounder(7600530.24) - } - public onVenus(): number { - return this.rounder(19413907.2) - } - public onEarth(): number { - return this.rounder(31558149.76) - } - public onMars(): number { - return this.rounder(59354294.4) - } - public onJupiter(): number { - return this.rounder(374335776.0) - } - public onSaturn(): number { - return this.rounder(929596608.0) - } - public onUranus(): number { - return this.rounder(2661041808.0) - } - public onNeptune(): number { - return this.rounder(5200418592.0) - } -} diff --git a/exercises/practice/space-age/space-age.test.ts b/exercises/practice/space-age/space-age.test.ts index 176336646..9867c3228 100644 --- a/exercises/practice/space-age/space-age.test.ts +++ b/exercises/practice/space-age/space-age.test.ts @@ -1,55 +1,35 @@ -import SpaceAge from './space-age' +import { age } from './space-age' describe('Space Age', () => { - it('age in seconds', () => { - const age = new SpaceAge(1000000) - expect(age.seconds).toEqual(1000000) + test('age on Earth', () => { + expect(age('earth', 1000000000)).toEqual(31.69) }) - xit('age in earth years', () => { - const age = new SpaceAge(1000000000) - expect(age.onEarth()).toEqual(31.69) + xtest('age on Mercury', () => { + expect(age('mercury', 2134835688)).toEqual(280.88) }) - xit('age in mercury years', () => { - const age = new SpaceAge(2134835688) - expect(age.onEarth()).toEqual(67.65) - expect(age.onMercury()).toEqual(280.88) + xtest('age on Venus', () => { + expect(age('venus', 189839836)).toEqual(9.78) }) - xit('age in venus years', () => { - const age = new SpaceAge(189839836) - expect(age.onEarth()).toEqual(6.02) - expect(age.onVenus()).toEqual(9.78) + xtest('age on Mars', () => { + expect(age('mars', 2129871239)).toEqual(35.88) }) - xit('age in mars years', () => { - const age = new SpaceAge(2329871239) - expect(age.onEarth()).toEqual(73.83) - expect(age.onMars()).toEqual(39.25) + xtest('age on Jupiter', () => { + expect(age('jupiter', 901876382)).toEqual(2.41) }) - xit('age in jupiter years', () => { - const age = new SpaceAge(901876382) - expect(age.onEarth()).toEqual(28.58) - expect(age.onJupiter()).toEqual(2.41) + xtest('age on Saturn', () => { + expect(age('saturn', 2000000000)).toEqual(2.15) }) - xit('age in saturn years', () => { - const age = new SpaceAge(3000000000) - expect(age.onEarth()).toEqual(95.06) - expect(age.onSaturn()).toEqual(3.23) + xtest('age on Uranus', () => { + expect(age('uranus', 1210123456)).toEqual(0.46) }) - xit('age in uranus years', () => { - const age = new SpaceAge(3210123456) - expect(age.onEarth()).toEqual(101.72) - expect(age.onUranus()).toEqual(1.21) - }) - - xit('age in neptune year', () => { - const age = new SpaceAge(8210123456) - expect(age.onEarth()).toEqual(260.16) - expect(age.onNeptune()).toEqual(1.58) + xtest('age on Neptune', () => { + expect(age('neptune', 1821023456)).toEqual(0.35) }) }) diff --git a/exercises/practice/space-age/space-age.ts b/exercises/practice/space-age/space-age.ts index e69de29bb..46fc44ac6 100644 --- a/exercises/practice/space-age/space-age.ts +++ b/exercises/practice/space-age/space-age.ts @@ -0,0 +1,3 @@ +export function age(planet: unknown, seconds: unknown): unknown { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/spiral-matrix/.meta/proof.ci.ts b/exercises/practice/spiral-matrix/.meta/proof.ci.ts new file mode 100644 index 000000000..b2a3b56ed --- /dev/null +++ b/exercises/practice/spiral-matrix/.meta/proof.ci.ts @@ -0,0 +1,29 @@ +export function ofSize(size: number): number[][] { + const spiral = Array(size) + .fill(Array()) + .map(() => Array(size).fill(0)) + + const totalNumbers = size ** 2 + let currentNumber = 1 + let topLeft = 0 + let bottomRight = size - 1 + + while (currentNumber <= totalNumbers) { + for (let x = topLeft; x <= bottomRight; x++) { + spiral[topLeft][x] = currentNumber++ + } + for (let y = topLeft + 1; y <= bottomRight; y++) { + spiral[y][bottomRight] = currentNumber++ + } + for (let x = bottomRight - 1; x >= topLeft; x--) { + spiral[bottomRight][x] = currentNumber++ + } + for (let y = bottomRight - 1; y >= topLeft + 1; y--) { + spiral[y][topLeft] = currentNumber++ + } + topLeft++ + bottomRight-- + } + + return spiral +} diff --git a/exercises/practice/spiral-matrix/spiral-matrix.example.ts b/exercises/practice/spiral-matrix/spiral-matrix.example.ts deleted file mode 100644 index 45ebfafb5..000000000 --- a/exercises/practice/spiral-matrix/spiral-matrix.example.ts +++ /dev/null @@ -1,33 +0,0 @@ -class SpiralMatrix { - public static ofSize(size: number): number[][] { - const spiral = Array(size) - .fill(Array()) - .map(() => Array(size).fill(0)) - - const totalNumbers = size ** 2 - let currentNumber = 1 - let topLeft = 0 - let bottomRight = size - 1 - - while (currentNumber <= totalNumbers) { - for (let x = topLeft; x <= bottomRight; x++) { - spiral[topLeft][x] = currentNumber++ - } - for (let y = topLeft + 1; y <= bottomRight; y++) { - spiral[y][bottomRight] = currentNumber++ - } - for (let x = bottomRight - 1; x >= topLeft; x--) { - spiral[bottomRight][x] = currentNumber++ - } - for (let y = bottomRight - 1; y >= topLeft + 1; y--) { - spiral[y][topLeft] = currentNumber++ - } - topLeft++ - bottomRight-- - } - - return spiral - } -} - -export default SpiralMatrix diff --git a/exercises/practice/spiral-matrix/spiral-matrix.test.ts b/exercises/practice/spiral-matrix/spiral-matrix.test.ts index 651cf7813..22f9df559 100644 --- a/exercises/practice/spiral-matrix/spiral-matrix.test.ts +++ b/exercises/practice/spiral-matrix/spiral-matrix.test.ts @@ -1,15 +1,15 @@ -import SpiralMatrix from './spiral-matrix' +import { ofSize } from './spiral-matrix' describe('Spiral Matrix', () => { it('empty spiral', () => { const expected: number[][] = [] - const actual = SpiralMatrix.ofSize(0) + const actual = ofSize(0) expect(actual).toEqual(expected) }) xit('trivial spiral', () => { const expected = [[1]] - const actual = SpiralMatrix.ofSize(1) + const actual = ofSize(1) expect(actual).toEqual(expected) }) @@ -18,7 +18,7 @@ describe('Spiral Matrix', () => { [1, 2], [4, 3], ] - const actual = SpiralMatrix.ofSize(2) + const actual = ofSize(2) expect(actual).toEqual(expected) }) @@ -28,7 +28,7 @@ describe('Spiral Matrix', () => { [8, 9, 4], [7, 6, 5], ] - const actual = SpiralMatrix.ofSize(3) + const actual = ofSize(3) expect(actual).toEqual(expected) }) @@ -39,7 +39,7 @@ describe('Spiral Matrix', () => { [11, 16, 15, 6], [10, 9, 8, 7], ] - const actual = SpiralMatrix.ofSize(4) + const actual = ofSize(4) expect(actual).toEqual(expected) }) @@ -51,7 +51,7 @@ describe('Spiral Matrix', () => { [14, 23, 22, 21, 8], [13, 12, 11, 10, 9], ] - const actual = SpiralMatrix.ofSize(5) + const actual = ofSize(5) expect(expected).toEqual(actual) }) }) diff --git a/exercises/practice/spiral-matrix/spiral-matrix.ts b/exercises/practice/spiral-matrix/spiral-matrix.ts index e69de29bb..29ed5fb77 100644 --- a/exercises/practice/spiral-matrix/spiral-matrix.ts +++ b/exercises/practice/spiral-matrix/spiral-matrix.ts @@ -0,0 +1,3 @@ +export function ofSize() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/strain/strain.example.ts b/exercises/practice/strain/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/strain/strain.example.ts rename to exercises/practice/strain/.meta/proof.ci.ts diff --git a/exercises/practice/strain/strain.ts b/exercises/practice/strain/strain.ts index e69de29bb..f73efc4ae 100644 --- a/exercises/practice/strain/strain.ts +++ b/exercises/practice/strain/strain.ts @@ -0,0 +1,7 @@ +export function keep() { + throw new Error('Remove this statement and implement this function') +} + +export function discard() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/sublist/sublist.example.ts b/exercises/practice/sublist/.meta/proof.ci.ts similarity index 95% rename from exercises/practice/sublist/sublist.example.ts rename to exercises/practice/sublist/.meta/proof.ci.ts index c76431065..e33fb0ad2 100644 --- a/exercises/practice/sublist/sublist.example.ts +++ b/exercises/practice/sublist/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -class List { +export class List { private readonly list: number[] constructor(...list: number[]) { @@ -26,5 +26,3 @@ class List { return !!listOne.join().match(listTwo.join()) } } - -export default List diff --git a/exercises/practice/sublist/sublist.test.ts b/exercises/practice/sublist/sublist.test.ts index 0de878403..fc48baf4f 100644 --- a/exercises/practice/sublist/sublist.test.ts +++ b/exercises/practice/sublist/sublist.test.ts @@ -1,4 +1,4 @@ -import List from './sublist' +import { List } from './sublist' describe('Sublist', () => { it('empty lists', () => { diff --git a/exercises/practice/sublist/sublist.ts b/exercises/practice/sublist/sublist.ts index e69de29bb..eb492296f 100644 --- a/exercises/practice/sublist/sublist.ts +++ b/exercises/practice/sublist/sublist.ts @@ -0,0 +1,9 @@ +export class List { + constructor() { + throw new Error('Remove this statement and implement this function') + } + + compare() { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/sum-of-multiples/.meta/proof.ci.ts b/exercises/practice/sum-of-multiples/.meta/proof.ci.ts new file mode 100644 index 000000000..aa4596aa6 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.meta/proof.ci.ts @@ -0,0 +1,9 @@ +export const sum = (multiples: number[], limit: number) => { + let sum = 0 + for (let i = 1; i < limit; i++) { + if (multiples.some((multiple) => i % multiple === 0)) { + sum += i + } + } + return sum +} diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples.example.ts b/exercises/practice/sum-of-multiples/sum-of-multiples.example.ts deleted file mode 100644 index 232ad2e0b..000000000 --- a/exercises/practice/sum-of-multiples/sum-of-multiples.example.ts +++ /dev/null @@ -1,22 +0,0 @@ -class SumOfMultiples { - private numbers: number[] - - constructor(numbers: number[]) { - this.numbers = numbers - } - - public to(limit: number): number { - const multiples = new Set() - this.numbers.forEach((n: number) => { - let i = n - while (i < limit) { - multiples.add(i) - i += n - } - }) - return [...multiples].reduce((a, b) => a + b, 0) - } -} - -export default (numbers: number[]): SumOfMultiples => - new SumOfMultiples(numbers) diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples.test.ts b/exercises/practice/sum-of-multiples/sum-of-multiples.test.ts index 97faf73dc..35a866ad2 100644 --- a/exercises/practice/sum-of-multiples/sum-of-multiples.test.ts +++ b/exercises/practice/sum-of-multiples/sum-of-multiples.test.ts @@ -1,39 +1,67 @@ -import SumOfMultiples from './sum-of-multiples' +import { sum } from './sum-of-multiples' -describe('SumOfMultiples', () => { - it('to 1', () => { - expect(SumOfMultiples([3, 5]).to(1)).toBe(0) +describe('Sum Of Multiples', () => { + test('no multiples within limit', () => { + expect(sum([3, 5], 1)).toEqual(0) }) - xit('to 3', () => { - expect(SumOfMultiples([3, 5]).to(4)).toBe(3) + xtest('one factor has multiples within limit', () => { + expect(sum([3, 5], 4)).toEqual(3) }) - xit('to 10', () => { - expect(SumOfMultiples([3, 5]).to(10)).toBe(23) + xtest('more than one multiple within limit', () => { + expect(sum([3], 7)).toEqual(9) }) - xit('to 100', () => { - expect(SumOfMultiples([3, 5]).to(100)).toBe(2318) + xtest('more than one factor with multiples within limit', () => { + expect(sum([3, 5], 10)).toEqual(23) }) - xit('to 1000', () => { - expect(SumOfMultiples([3, 5]).to(1000)).toBe(233168) + xtest('each multiple is only counted once', () => { + expect(sum([3, 5], 100)).toEqual(2318) }) - xit('[7, 13, 17] to 20', () => { - expect(SumOfMultiples([7, 13, 17]).to(20)).toBe(51) + xtest('a much larger limit', () => { + expect(sum([3, 5], 1000)).toEqual(233168) }) - xit('[4, 6] to 15', () => { - expect(SumOfMultiples([4, 6]).to(15)).toBe(30) + xtest('three factors', () => { + expect(sum([7, 13, 17], 20)).toEqual(51) }) - xit('[5, 6, 8] to 150', () => { - expect(SumOfMultiples([5, 6, 8]).to(150)).toBe(4419) + xtest('factors not relatively prime', () => { + expect(sum([4, 6], 15)).toEqual(30) }) - xit('[43, 47] to 10000', () => { - expect(SumOfMultiples([43, 47]).to(10000)).toBe(2203160) + xtest('some pairs of factors relatively prime and some not', () => { + expect(sum([5, 6, 8], 150)).toEqual(4419) + }) + + xtest('one factor is a multiple of another', () => { + expect(sum([5, 25], 51)).toEqual(275) + }) + + xtest('much larger factors', () => { + expect(sum([43, 47], 10000)).toEqual(2203160) + }) + + xtest('all numbers are multiples of 1', () => { + expect(sum([1], 100)).toEqual(4950) + }) + + xtest('no factors means an empty sum', () => { + expect(sum([], 10000)).toEqual(0) + }) + + xtest('the only multiple of 0 is 0', () => { + expect(sum([0], 1)).toEqual(0) + }) + + xtest('the factor 0 does not affect the sum of multiples of other factors', () => { + expect(sum([3, 0], 4)).toEqual(3) + }) + + xtest('solutions using include-exclude must extend to cardinality greater than 3', () => { + expect(sum([2, 3, 5, 7, 11], 10000)).toEqual(39614537) }) }) diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples.ts b/exercises/practice/sum-of-multiples/sum-of-multiples.ts index e69de29bb..39f69ba0a 100644 --- a/exercises/practice/sum-of-multiples/sum-of-multiples.ts +++ b/exercises/practice/sum-of-multiples/sum-of-multiples.ts @@ -0,0 +1,3 @@ +export function sum() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/transpose/transpose.example.ts b/exercises/practice/transpose/.meta/proof.ci.ts similarity index 86% rename from exercises/practice/transpose/transpose.example.ts rename to exercises/practice/transpose/.meta/proof.ci.ts index 408f0c058..081e13738 100644 --- a/exercises/practice/transpose/transpose.example.ts +++ b/exercises/practice/transpose/.meta/proof.ci.ts @@ -10,12 +10,10 @@ const fillColumn: (t: string[], l: string, r: number) => void = ( } } -function transpose(lines: string[]): string[] { +export function transpose(lines: string[]): string[] { const transposed: string[] = [] for (let toColumn = 0; toColumn < lines.length; toColumn++) fillColumn(transposed, lines[toColumn], toColumn) return transposed } - -export default transpose diff --git a/exercises/practice/transpose/transpose.test.ts b/exercises/practice/transpose/transpose.test.ts index f218be7eb..ead8a8f53 100644 --- a/exercises/practice/transpose/transpose.test.ts +++ b/exercises/practice/transpose/transpose.test.ts @@ -1,4 +1,4 @@ -import transpose from './transpose' +import { transpose } from './transpose' describe('Transpose', () => { it('empty string', () => { diff --git a/exercises/practice/transpose/transpose.ts b/exercises/practice/transpose/transpose.ts index e96c30b35..f3c83bb96 100644 --- a/exercises/practice/transpose/transpose.ts +++ b/exercises/practice/transpose/transpose.ts @@ -1,5 +1,3 @@ -function transpose(_lines: string[]): string[] { - return [] +export function transpose() { + throw new Error('Remove this statement and implement this function') } - -export default transpose diff --git a/exercises/practice/triangle/.meta/proof.ci.ts b/exercises/practice/triangle/.meta/proof.ci.ts new file mode 100644 index 000000000..9dd2ab19a --- /dev/null +++ b/exercises/practice/triangle/.meta/proof.ci.ts @@ -0,0 +1,38 @@ +export class Triangle { + sides: number[] + + constructor(...sides: number[]) { + this.sides = sides + } + + get isValid() { + const [s1, s2, s3] = this.sides + const sidesArePositive = s1 > 0 && s2 > 0 && s3 > 0 + const validatesTriangleInequality = + s1 + s2 >= s3 && s1 + s3 >= s2 && s2 + s3 >= s1 + return sidesArePositive && validatesTriangleInequality + } + + get isEquilateral() { + if (!this.isValid) { + return false + } + const [s1, s2, s3] = this.sides + return s1 === s2 && s2 === s3 && s1 === s3 + } + + get isIsosceles() { + if (!this.isValid) { + return false + } + const [s1, s2, s3] = this.sides + return s1 === s2 || s1 === s3 || s2 === s3 + } + + get isScalene() { + if (!this.isValid) { + return false + } + return !this.isIsosceles + } +} diff --git a/exercises/practice/triangle/triangle.example.ts b/exercises/practice/triangle/triangle.example.ts deleted file mode 100644 index 8d054a9db..000000000 --- a/exercises/practice/triangle/triangle.example.ts +++ /dev/null @@ -1,55 +0,0 @@ -enum Type { - Equilateral = 'equilateral', - Isosceles = 'isosceles', - Scalene = 'scalene', -} - -export default class Triangle { - private readonly sides: number[] - - constructor(...sides: number[]) { - this.sides = sides - } - - public kind(): Type { - if (this.isIllegal()) { - throw new TypeError('illegal') - } - - if (this.isEquilateral()) { - return Type.Equilateral - } - - if (this.isIsosceles()) { - return Type.Isosceles - } - - return Type.Scalene - } - - private isIllegal(): boolean { - return this.violatesInequality() || this.hasImpossibleSides() - } - - private violatesInequality(): boolean { - const [a, b, c] = this.sides - return a + b < c || a + c < b || b + c < a - } - - private hasImpossibleSides(): boolean { - const [a, b, c] = this.sides - return a <= 0 || b <= 0 || c <= 0 - } - - private isEquilateral(): boolean { - return this.uniqueSidesLength() === 1 - } - - private isIsosceles(): boolean { - return this.uniqueSidesLength() === 2 - } - - private uniqueSidesLength(): number { - return new Set(this.sides).size - } -} diff --git a/exercises/practice/triangle/triangle.test.ts b/exercises/practice/triangle/triangle.test.ts index 67b719d8d..0a921fb4d 100644 --- a/exercises/practice/triangle/triangle.test.ts +++ b/exercises/practice/triangle/triangle.test.ts @@ -1,78 +1,104 @@ -import Triangle from './triangle' +import { Triangle } from './triangle' describe('Triangle', () => { - it('equilateral triangles have equal sides', () => { - const triangle = new Triangle(2, 2, 2) - expect(triangle.kind()).toEqual('equilateral') - }) + describe('equilateral triangle', () => { + test('all sides are equal', () => { + const triangle = new Triangle(2, 2, 2) + expect(triangle.isEquilateral).toBe(true) + }) - xit('larger equilateral triangles also have equal sides', () => { - const triangle = new Triangle(10, 10, 10) - expect(triangle.kind()).toEqual('equilateral') - }) + xtest('any side is unequal', () => { + const triangle = new Triangle(2, 3, 2) + expect(triangle.isEquilateral).toBe(false) + }) - xit('isosceles triangles have last two sides equal', () => { - const triangle = new Triangle(3, 4, 4) - expect(triangle.kind()).toEqual('isosceles') - }) + xtest('no sides are equal', () => { + const triangle = new Triangle(5, 4, 6) + expect(triangle.isEquilateral).toBe(false) + }) - xit('isosceles trianges have first and last sides equal', () => { - const triangle = new Triangle(4, 3, 4) - expect(triangle.kind()).toEqual('isosceles') - }) + xtest('all zero sides is not a triangle', () => { + const triangle = new Triangle(0, 0, 0) + expect(triangle.isEquilateral).toBe(false) + }) - xit('isosceles triangles have two first sides equal', () => { - const triangle = new Triangle(4, 4, 3) - expect(triangle.kind()).toEqual('isosceles') + xtest('sides may be floats', () => { + const triangle = new Triangle(0.5, 0.5, 0.5) + expect(triangle.isEquilateral).toBe(true) + }) }) - xit('isosceles triangles have in fact exactly two sides equal', () => { - const triangle = new Triangle(10, 10, 2) - expect(triangle.kind()).toEqual('isosceles') - }) + describe('isosceles triangle', () => { + xtest('last two sides are equal', () => { + const triangle = new Triangle(3, 4, 4) + expect(triangle.isIsosceles).toBe(true) + }) - xit('scalene triangles have no equal sides', () => { - const triangle = new Triangle(3, 4, 5) - expect(triangle.kind()).toEqual('scalene') - }) + xtest('first two sides are equal', () => { + const triangle = new Triangle(4, 4, 3) + expect(triangle.isIsosceles).toBe(true) + }) - xit('scalene triangles have no equal sides at a larger scale too', () => { - const triangle = new Triangle(10, 11, 12) - expect(triangle.kind()).toEqual('scalene') - }) + xtest('first and last sides are equal', () => { + const triangle = new Triangle(4, 3, 4) + expect(triangle.isIsosceles).toBe(true) + }) - xit('scalene triangles have no equal sides in descending order either', () => { - const triangle = new Triangle(5, 4, 2) - expect(triangle.kind()).toEqual('scalene') - }) + xtest('equilateral triangles are also isosceles', () => { + const triangle = new Triangle(4, 4, 4) + expect(triangle.isIsosceles).toBe(true) + }) - xit('very small triangles are legal', () => { - const triangle = new Triangle(0.4, 0.6, 0.3) - expect(triangle.kind()).toEqual('scalene') - }) + xtest('no sides are equal', () => { + const triangle = new Triangle(2, 3, 4) + expect(triangle.isIsosceles).toBe(false) + }) - xit('test triangles with no size are illegal', () => { - const triangle = new Triangle(0, 0, 0) - expect(triangle.kind.bind(triangle)).toThrow() - }) + xtest('first triangle inequality violation', () => { + const triangle = new Triangle(1, 1, 3) + expect(triangle.isIsosceles).toBe(false) + }) - xit('triangles with negative sides are illegal', () => { - const triangle = new Triangle(3, 4, -5) - expect(triangle.kind.bind(triangle)).toThrow() - }) + xtest('second triangle inequality violation', () => { + const triangle = new Triangle(1, 3, 1) + expect(triangle.isIsosceles).toBe(false) + }) - xit('triangles violating triangle inequality are illegal', () => { - const triangle = new Triangle(1, 1, 3) - expect(triangle.kind.bind(triangle)).toThrow() - }) + xtest('third triangle inequality violation', () => { + const triangle = new Triangle(3, 1, 1) + expect(triangle.isIsosceles).toBe(false) + }) - xit('triangles violating triangle inequality are illegal 2', () => { - const triangle = new Triangle(7, 3, 2) - expect(triangle.kind.bind(triangle)).toThrow() + xtest('sides may be floats', () => { + const triangle = new Triangle(0.5, 0.4, 0.5) + expect(triangle.isIsosceles).toBe(true) + }) }) - xit('triangles violating triangle inequality are illegal 3', () => { - const triangle = new Triangle(10, 1, 3) - expect(triangle.kind.bind(triangle)).toThrow() + describe('scalene triangle', () => { + xtest('no sides are equal', () => { + const triangle = new Triangle(5, 4, 6) + expect(triangle.isScalene).toBe(true) + }) + + xtest('all sides are equal', () => { + const triangle = new Triangle(4, 4, 4) + expect(triangle.isScalene).toBe(false) + }) + + xtest('two sides are equal', () => { + const triangle = new Triangle(4, 4, 3) + expect(triangle.isScalene).toBe(false) + }) + + xtest('may not violate triangle inequality', () => { + const triangle = new Triangle(7, 3, 2) + expect(triangle.isScalene).toBe(false) + }) + + xtest('sides may be floats', () => { + const triangle = new Triangle(0.5, 0.4, 0.6) + expect(triangle.isScalene).toBe(true) + }) }) }) diff --git a/exercises/practice/triangle/triangle.ts b/exercises/practice/triangle/triangle.ts index 5ef836ce7..8b47b820c 100644 --- a/exercises/practice/triangle/triangle.ts +++ b/exercises/practice/triangle/triangle.ts @@ -1,11 +1,17 @@ -export default class Triangle { - sides: number[] +export class Triangle { + constructor(...sides) { + throw new Error('Remove this statement and implement this function') + } + + get isEquilateral() { + throw new Error('Remove this statement and implement this function') + } - constructor(...sides: number[]) { - this.sides = sides + get isIsosceles() { + throw new Error('Remove this statement and implement this function') } - kind() { - return '' + get isScalene() { + throw new Error('Remove this statement and implement this function') } } diff --git a/exercises/practice/twelve-days/.meta/proof.ci.ts b/exercises/practice/twelve-days/.meta/proof.ci.ts new file mode 100644 index 000000000..04b559838 --- /dev/null +++ b/exercises/practice/twelve-days/.meta/proof.ci.ts @@ -0,0 +1,50 @@ +const days = [ + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + 'seventh', + 'eighth', + 'ninth', + 'tenth', + 'eleventh', + 'twelfth', +] as const + +const gifts = [ + 'a Partridge in a Pear Tree.', + 'two Turtle Doves, ', + 'three French Hens, ', + 'four Calling Birds, ', + 'five Gold Rings, ', + 'six Geese-a-Laying, ', + 'seven Swans-a-Swimming, ', + 'eight Maids-a-Milking, ', + 'nine Ladies Dancing, ', + 'ten Lords-a-Leaping, ', + 'eleven Pipers Piping, ', + 'twelve Drummers Drumming, ', +] as const + +export function recite(startVerse: number, endVerse: number): string { + let lyrics = reciteVerse(startVerse) + for (let i = startVerse + 1; i <= endVerse; i++) { + lyrics += reciteVerse(i) + } + return lyrics +} + +function reciteVerse(verse: number): string { + let result = + 'On the ' + days[verse - 1] + ' day of Christmas my true love gave to me: ' + for (let i = verse; i > 0; i--) { + if (verse !== 1 && i === 1) { + result += 'and ' + } + result += gifts[i - 1] + } + result += '\n' + return result +} diff --git a/exercises/practice/twelve-days/twelve-days.example.ts b/exercises/practice/twelve-days/twelve-days.example.ts deleted file mode 100644 index d0f2d9cd5..000000000 --- a/exercises/practice/twelve-days/twelve-days.example.ts +++ /dev/null @@ -1,56 +0,0 @@ -class TwelveDays { - private static days = [ - 'first', - 'second', - 'third', - 'fourth', - 'fifth', - 'sixth', - 'seventh', - 'eighth', - 'ninth', - 'tenth', - 'eleventh', - 'twelfth', - ] as const - - private static gifts = [ - 'a Partridge in a Pear Tree.', - 'two Turtle Doves, ', - 'three French Hens, ', - 'four Calling Birds, ', - 'five Gold Rings, ', - 'six Geese-a-Laying, ', - 'seven Swans-a-Swimming, ', - 'eight Maids-a-Milking, ', - 'nine Ladies Dancing, ', - 'ten Lords-a-Leaping, ', - 'eleven Pipers Piping, ', - 'twelve Drummers Drumming, ', - ] as const - - public static recite(startVerse: number, endVerse: number): string { - let lyrics = this.reciteVerse(startVerse) - for (let i = startVerse + 1; i <= endVerse; i++) { - lyrics += this.reciteVerse(i) - } - return lyrics - } - - private static reciteVerse(verse: number): string { - let result = - 'On the ' + - this.days[verse - 1] + - ' day of Christmas my true love gave to me: ' - for (let i = verse; i > 0; i--) { - if (verse !== 1 && i === 1) { - result += 'and ' - } - result += this.gifts[i - 1] - } - result += '\n' - return result - } -} - -export default TwelveDays diff --git a/exercises/practice/twelve-days/twelve-days.test.ts b/exercises/practice/twelve-days/twelve-days.test.ts index 586253dd0..d49b63190 100644 --- a/exercises/practice/twelve-days/twelve-days.test.ts +++ b/exercises/practice/twelve-days/twelve-days.test.ts @@ -1,76 +1,76 @@ -import TwelveDays from './twelve-days' +import { recite } from './twelve-days' describe('verse', () => { it('first day a partridge in a pear tree', () => { const expected = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(1, 1)).toEqual(expected) + expect(recite(1, 1)).toEqual(expected) }) xit('second day two turtle doves', () => { const expected = 'On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(2, 2)).toEqual(expected) + expect(recite(2, 2)).toEqual(expected) }) xit('third day three french hens', () => { const expected = 'On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(3, 3)).toEqual(expected) + expect(recite(3, 3)).toEqual(expected) }) xit('fourth day four calling birds', () => { const expected = 'On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(4, 4)).toEqual(expected) + expect(recite(4, 4)).toEqual(expected) }) xit('fifth day five gold rings', () => { const expected = 'On the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(5, 5)).toEqual(expected) + expect(recite(5, 5)).toEqual(expected) }) xit('sixth day six geese-a-laying', () => { const expected = 'On the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(6, 6)).toEqual(expected) + expect(recite(6, 6)).toEqual(expected) }) xit('seventh day seven swans-a-swimming', () => { const expected = 'On the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(7, 7)).toEqual(expected) + expect(recite(7, 7)).toEqual(expected) }) xit('eighth day eight maids-a-milking', () => { const expected = 'On the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(8, 8)).toEqual(expected) + expect(recite(8, 8)).toEqual(expected) }) xit('ninth day nine ladies dancing', () => { const expected = 'On the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(9, 9)).toEqual(expected) + expect(recite(9, 9)).toEqual(expected) }) xit('tenth day ten lords-a-leaping', () => { const expected = 'On the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(10, 10)).toEqual(expected) + expect(recite(10, 10)).toEqual(expected) }) xit('eleventh day eleven pipers piping', () => { const expected = 'On the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(11, 11)).toEqual(expected) + expect(recite(11, 11)).toEqual(expected) }) xit('twelfth day twelve drummers drumming', () => { const expected = 'On the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(12, 12)).toEqual(expected) + expect(recite(12, 12)).toEqual(expected) }) }) @@ -78,18 +78,18 @@ describe('lyrics', () => { xit('recites first three verses of the song', () => { const expected = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\nOn the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(1, 3)).toEqual(expected) + expect(recite(1, 3)).toEqual(expected) }) xit('recites three verses from the middle of the song', () => { const expected = 'On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(4, 6)).toEqual(expected) + expect(recite(4, 6)).toEqual(expected) }) xit('recites the whole song', () => { const expected = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\nOn the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' - expect(TwelveDays.recite(1, 12)).toEqual(expected) + expect(recite(1, 12)).toEqual(expected) }) }) diff --git a/exercises/practice/twelve-days/twelve-days.ts b/exercises/practice/twelve-days/twelve-days.ts index a690838f1..efd481905 100644 --- a/exercises/practice/twelve-days/twelve-days.ts +++ b/exercises/practice/twelve-days/twelve-days.ts @@ -1,7 +1,3 @@ -class TwelveDays { - static recite(/* Parameters go here */) { - // Your code here - } +export function recite() { + throw new Error('Remove this statement and implement this function') } - -export default TwelveDays diff --git a/exercises/practice/two-bucket/two-bucket.example.ts b/exercises/practice/two-bucket/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/two-bucket/two-bucket.example.ts rename to exercises/practice/two-bucket/.meta/proof.ci.ts diff --git a/exercises/practice/two-bucket/two-bucket.test.ts b/exercises/practice/two-bucket/two-bucket.test.ts index 431b76f54..6f6027fcf 100644 --- a/exercises/practice/two-bucket/two-bucket.test.ts +++ b/exercises/practice/two-bucket/two-bucket.test.ts @@ -1,21 +1,25 @@ -import { TwoBucket, Bucket } from './two-bucket' +import { TwoBucket } from './two-bucket' describe('TwoBucket', () => { - describe('works for input of 3, 5, 1', () => { + describe('Measure using bucket one of size 3 and bucket two of size 5', () => { const buckOne = 3 const buckTwo = 5 const goal = 1 - it('starting with bucket one', () => { - const starterBuck = Bucket.One // indicates which bucket to fill first + test('start with bucket one', () => { + // indicates which bucket to fill first + const starterBuck = 'one' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) - expect(twoBucket.moves()).toEqual(4) // includes the first fill - expect(twoBucket.goalBucket).toEqual('one') // which bucket should end up with the desired # of liters - expect(twoBucket.otherBucket).toEqual(5) // leftover value in the "other" bucket once the goal has been reached + // includes the first fill + expect(twoBucket.moves()).toEqual(4) + // which bucket should end up with the desired # of liters + expect(twoBucket.goalBucket).toEqual('one') + // leftover value in the "other" bucket once the goal has been reached + expect(twoBucket.otherBucket).toEqual(5) }) - xit('starting with bucket two', () => { - const starterBuck = Bucket.Two + xtest('start with bucket two', () => { + const starterBuck = 'two' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(8) expect(twoBucket.goalBucket).toEqual('two') @@ -23,25 +27,70 @@ describe('TwoBucket', () => { }) }) - describe('works for input of 7, 11, 2', () => { + describe('Measure using bucket one of size 7 and bucket two of size 11', () => { const buckOne = 7 const buckTwo = 11 const goal = 2 - xit('starting with bucket one', () => { - const starterBuck = Bucket.One + xtest('start with bucket one', () => { + const starterBuck = 'one' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(14) expect(twoBucket.goalBucket).toEqual('one') expect(twoBucket.otherBucket).toEqual(11) }) - xit('starting with bucket two', () => { - const starterBuck = Bucket.Two + xtest('start with bucket two', () => { + const starterBuck = 'two' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(18) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(7) }) }) + + describe('Measure one step using bucket one of size 1 and bucket two of size 3', () => { + xtest('start with bucket two', () => { + const twoBucket = new TwoBucket(1, 3, 3, 'two') + expect(twoBucket.moves()).toEqual(1) + expect(twoBucket.goalBucket).toEqual('two') + expect(twoBucket.otherBucket).toEqual(0) + }) + }) + + describe('Measure using bucket one of size 2 and bucket two of size 3', () => { + test.skip('start with bucket one and end with bucket two', () => { + const twoBucket = new TwoBucket(2, 3, 3, 'one') + expect(twoBucket.moves()).toEqual(2) + expect(twoBucket.goalBucket).toEqual('two') + expect(twoBucket.otherBucket).toEqual(2) + }) + }) + + describe('Reachability', () => { + const buckOne = 6 + const buckTwo = 15 + const starterBuck = 'one' + + test.skip('Not possible to reach the goal', () => { + const goal = 5 + const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) + expect(() => twoBucket.moves()).toThrow() + }) + + xtest('With the same buckets but a different goal, then it is possible', () => { + const goal = 9 + const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) + expect(twoBucket.moves()).toEqual(10) + expect(twoBucket.goalBucket).toEqual('two') + expect(twoBucket.otherBucket).toEqual(0) + }) + }) + + describe('Goal larger than both buckets', () => { + test.skip('Is impossible', () => { + const twoBucket = new TwoBucket(5, 7, 8, 'one') + expect(() => twoBucket.moves()).toThrow() + }) + }) }) diff --git a/exercises/practice/two-bucket/two-bucket.ts b/exercises/practice/two-bucket/two-bucket.ts index e69de29bb..f8fb690fa 100644 --- a/exercises/practice/two-bucket/two-bucket.ts +++ b/exercises/practice/two-bucket/two-bucket.ts @@ -0,0 +1,17 @@ +export class TwoBucket { + constructor() { + throw new Error('Remove this statement and implement this function') + } + + moves() { + throw new Error('Remove this statement and implement this function') + } + + get goalBucket() { + throw new Error('Remove this statement and implement this function') + } + + get otherBucket() { + throw new Error('Remove this statement and implement this function') + } +} diff --git a/exercises/practice/two-fer/two-fer.example.ts b/exercises/practice/two-fer/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/two-fer/two-fer.example.ts rename to exercises/practice/two-fer/.meta/proof.ci.ts diff --git a/exercises/practice/variable-length-quantity/.meta/proof.ci.ts b/exercises/practice/variable-length-quantity/.meta/proof.ci.ts new file mode 100644 index 000000000..67a0110ea --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/proof.ci.ts @@ -0,0 +1,50 @@ +const LENGTH = 7 +const CONT_BITS = 1 << LENGTH +const DATA_BITS = CONT_BITS - 1 + +function encodeOne(val: number): number[] { + const buf: number[] = [] + let left = val + + while (left) { + const bits = (left & DATA_BITS) | CONT_BITS // set continuation everywhere + left = left >>> LENGTH + buf.push(bits) + } + buf[0] = buf[0] & DATA_BITS // cancel the last continuation + return buf.reverse() +} + +function decodeOne(buf: number[]): number { + let val = 0 + + for (let i = 0; i <= buf.length - 1; i++) { + val = (val << LENGTH) | (buf[i] & DATA_BITS) + } + return val >>> 0 // convert to unsigned 32-bit +} + +function encode(data: number[]): number[] { + let buf: number[] = [] + + for (let i = 0; i <= data.length - 1; i++) { + buf = buf.concat(encodeOne(data[i])) + } + return buf +} + +function decode(data: number[]): number[] { + let start = 0 + const vals = [] + + for (let i = 0; i < data.length; i++) { + if (~data[i] & CONT_BITS) { + vals.push(decodeOne(data.slice(start, i + 1))) + start = i + 1 + } + } + if (start < data.length) { + throw new Error('Incomplete sequence') + } + return vals +} diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity.example.ts b/exercises/practice/variable-length-quantity/variable-length-quantity.example.ts deleted file mode 100644 index fc11f09ae..000000000 --- a/exercises/practice/variable-length-quantity/variable-length-quantity.example.ts +++ /dev/null @@ -1,56 +0,0 @@ -class VariableLengthQuantity { - private static LENGTH = 7 - private static CONT_BITS = 1 << VariableLengthQuantity.LENGTH - private static DATA_BITS = VariableLengthQuantity.CONT_BITS - 1 - - private static buf: number[] = [] - - private static encodeOne(val: number): number[] { - this.buf = [] - let left = val - - while (left) { - const bits = (left & this.DATA_BITS) | this.CONT_BITS // set continuation everywhere - left = left >>> this.LENGTH - this.buf.push(bits) - } - this.buf[0] = this.buf[0] & this.DATA_BITS // cancel the last continuation - return this.buf.reverse() - } - - private static decodeOne(buf: number[]): number { - let val = 0 - - for (let i = 0; i <= buf.length - 1; i++) { - val = (val << this.LENGTH) | (buf[i] & this.DATA_BITS) - } - return val >>> 0 // convert to unsigned 32-bit - } - - public static encode(data: number[]): number[] { - let buf: number[] = [] - - for (let i = 0; i <= data.length - 1; i++) { - buf = buf.concat(this.encodeOne(data[i])) - } - return buf - } - - public static decode(data: number[]): number[] { - let start = 0 - const vals = [] - - for (let i = 0; i < data.length; i++) { - if (~data[i] & this.CONT_BITS) { - vals.push(this.decodeOne(data.slice(start, i + 1))) - start = i + 1 - } - } - if (start < data.length) { - throw new Error('Incomplete sequence') - } - return vals - } -} - -export default VariableLengthQuantity diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity.test.ts b/exercises/practice/variable-length-quantity/variable-length-quantity.test.ts index 1a885674c..777d7e3ff 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity.test.ts +++ b/exercises/practice/variable-length-quantity/variable-length-quantity.test.ts @@ -1,73 +1,73 @@ -import VLQ from './variable-length-quantity' +import { encode, decode } from './variable-length-quantity' describe('VariableLengthQuantity', () => { describe('Encode a series of integers, producing a series of bytes.', () => { it('zero', () => { - expect(VLQ.encode([0])).toEqual([0]) + expect(encode([0])).toEqual([0]) }) xit('arbitrary single byte', () => { - expect(VLQ.encode([0x40])).toEqual([0x40]) + expect(encode([0x40])).toEqual([0x40]) }) xit('largest single byte', () => { - expect(VLQ.encode([0x7f])).toEqual([0x7f]) + expect(encode([0x7f])).toEqual([0x7f]) }) xit('smallest double byte', () => { - expect(VLQ.encode([0x80])).toEqual([0x81, 0]) + expect(encode([0x80])).toEqual([0x81, 0]) }) xit('arbitrary double byte', () => { - expect(VLQ.encode([0x2000])).toEqual([0xc0, 0]) + expect(encode([0x2000])).toEqual([0xc0, 0]) }) xit('largest double byte', () => { - expect(VLQ.encode([0x3fff])).toEqual([0xff, 0x7f]) + expect(encode([0x3fff])).toEqual([0xff, 0x7f]) }) xit('smallest triple byte', () => { - expect(VLQ.encode([0x4000])).toEqual([0x81, 0x80, 0]) + expect(encode([0x4000])).toEqual([0x81, 0x80, 0]) }) xit('arbitrary triple byte', () => { - expect(VLQ.encode([0x100000])).toEqual([0xc0, 0x80, 0]) + expect(encode([0x100000])).toEqual([0xc0, 0x80, 0]) }) xit('largest triple byte', () => { - expect(VLQ.encode([0x1fffff])).toEqual([0xff, 0xff, 0x7f]) + expect(encode([0x1fffff])).toEqual([0xff, 0xff, 0x7f]) }) xit('smallest quadruple byte', () => { - expect(VLQ.encode([0x200000])).toEqual([0x81, 0x80, 0x80, 0]) + expect(encode([0x200000])).toEqual([0x81, 0x80, 0x80, 0]) }) xit('arbitrary quadruple byte', () => { - expect(VLQ.encode([0x8000000])).toEqual([0xc0, 0x80, 0x80, 0]) + expect(encode([0x8000000])).toEqual([0xc0, 0x80, 0x80, 0]) }) xit('largest quadruple byte', () => { - expect(VLQ.encode([0xfffffff])).toEqual([0xff, 0xff, 0xff, 0x7f]) + expect(encode([0xfffffff])).toEqual([0xff, 0xff, 0xff, 0x7f]) }) xit('smallest quintuple byte', () => { - expect(VLQ.encode([0x10000000])).toEqual([0x81, 0x80, 0x80, 0x80, 0]) + expect(encode([0x10000000])).toEqual([0x81, 0x80, 0x80, 0x80, 0]) }) xit('arbitrary quintuple byte', () => { - expect(VLQ.encode([0xff000000])).toEqual([0x8f, 0xf8, 0x80, 0x80, 0]) + expect(encode([0xff000000])).toEqual([0x8f, 0xf8, 0x80, 0x80, 0]) }) xit('maximum 32-bit integer input', () => { - expect(VLQ.encode([0xffffffff])).toEqual([0x8f, 0xff, 0xff, 0xff, 0x7f]) + expect(encode([0xffffffff])).toEqual([0x8f, 0xff, 0xff, 0xff, 0x7f]) }) xit('two single-byte values', () => { - expect(VLQ.encode([0x40, 0x7f])).toEqual([0x40, 0x7f]) + expect(encode([0x40, 0x7f])).toEqual([0x40, 0x7f]) }) xit('two multi-byte values', () => { - expect(VLQ.encode([0x4000, 0x123456])).toEqual([ + expect(encode([0x4000, 0x123456])).toEqual([ 0x81, 0x80, 0, @@ -96,40 +96,40 @@ describe('VariableLengthQuantity', () => { 0x80, 0, ] - expect(VLQ.encode(input)).toEqual(expected) + expect(encode(input)).toEqual(expected) }) }) describe('Decode a series of bytes, producing a series of integers.', () => { xit('one byte', () => { - expect(VLQ.decode([0x7f])).toEqual([0x7f]) + expect(decode([0x7f])).toEqual([0x7f]) }) xit('two bytes', () => { - expect(VLQ.decode([0xc0, 0])).toEqual([0x2000]) + expect(decode([0xc0, 0])).toEqual([0x2000]) }) xit('three bytes', () => { - expect(VLQ.decode([0xff, 0xff, 0x7f])).toEqual([0x1fffff]) + expect(decode([0xff, 0xff, 0x7f])).toEqual([0x1fffff]) }) xit('four bytes', () => { - expect(VLQ.decode([0x81, 0x80, 0x80, 0])).toEqual([0x200000]) + expect(decode([0x81, 0x80, 0x80, 0])).toEqual([0x200000]) }) xit('maximum 32-bit integer', () => { - expect(VLQ.decode([0x8f, 0xff, 0xff, 0xff, 0x7f])).toEqual([0xffffffff]) + expect(decode([0x8f, 0xff, 0xff, 0xff, 0x7f])).toEqual([0xffffffff]) }) xit('incomplete sequence causes error', () => { expect(() => { - VLQ.decode([0xff]) + decode([0xff]) }).toThrowError('Incomplete sequence') }) xit('incomplete sequence causes error, even if value is zero', () => { expect(() => { - VLQ.decode([0x80]) + decode([0x80]) }).toThrowError('Incomplete sequence') }) @@ -152,7 +152,7 @@ describe('VariableLengthQuantity', () => { 0, ] const expected = [0x2000, 0x123456, 0xfffffff, 0, 0x3fff, 0x4000] - expect(VLQ.decode(input)).toEqual(expected) + expect(decode(input)).toEqual(expected) }) }) }) diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity.ts b/exercises/practice/variable-length-quantity/variable-length-quantity.ts index e69de29bb..0d193e0fe 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity.ts +++ b/exercises/practice/variable-length-quantity/variable-length-quantity.ts @@ -0,0 +1,7 @@ +export function encode() { + throw new Error('Remove this statement and implement this function') +} + +export function decode() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/word-count/.meta/proof.ci.ts b/exercises/practice/word-count/.meta/proof.ci.ts new file mode 100644 index 000000000..e65ef9fe7 --- /dev/null +++ b/exercises/practice/word-count/.meta/proof.ci.ts @@ -0,0 +1,18 @@ +export function count(input: string): Map { + const inputArray = input + .toLowerCase() + .replace(/\n/m, ' ') + .replace(/\t/m, ' ') + .split(' ') + const inputMap = new Map() + + for (const each of inputArray) { + if (each === '') { + continue + } + const value = inputMap.get(each) || 0 + + inputMap.set(each.trim(), value + 1) + } + return inputMap +} diff --git a/exercises/practice/word-count/word-count.example.ts b/exercises/practice/word-count/word-count.example.ts deleted file mode 100644 index 86aae4b63..000000000 --- a/exercises/practice/word-count/word-count.example.ts +++ /dev/null @@ -1,22 +0,0 @@ -class Words { - public count(input: string): Map { - const inputArray = input - .toLowerCase() - .replace(/\n/m, ' ') - .replace(/\t/m, ' ') - .split(' ') - const inputMap = new Map() - - for (const each of inputArray) { - if (each === '') { - continue - } - const value = inputMap.get(each) || 0 - - inputMap.set(each.trim(), value + 1) - } - return inputMap - } -} - -export default Words diff --git a/exercises/practice/word-count/word-count.test.ts b/exercises/practice/word-count/word-count.test.ts index 2eae013ba..58a8d0f03 100644 --- a/exercises/practice/word-count/word-count.test.ts +++ b/exercises/practice/word-count/word-count.test.ts @@ -1,23 +1,21 @@ -import Words from './word-count' +import { count } from './word-count' describe('words()', () => { - const words = new Words() - it('counts one word', () => { const expectedCounts = new Map(Object.entries({ word: 1 })) - expect(words.count('word')).toEqual(expectedCounts) + expect(count('word')).toEqual(expectedCounts) }) xit('counts one of each', () => { const expectedCounts = new Map(Object.entries({ one: 1, of: 1, each: 1 })) - expect(words.count('one of each')).toEqual(expectedCounts) + expect(count('one of each')).toEqual(expectedCounts) }) xit('counts multiple occurrences', () => { const expectedCounts = new Map( Object.entries({ one: 1, fish: 4, two: 1, red: 1, blue: 1 }) ) - expect(words.count('one fish two fish red fish blue fish')).toEqual( + expect(count('one fish two fish red fish blue fish')).toEqual( expectedCounts ) }) @@ -33,48 +31,48 @@ describe('words()', () => { 'javascript!!&@$%^&': 1, }) ) - expect(words.count('car : carpet as java : javascript!!&@$%^&')).toEqual( + expect(count('car : carpet as java : javascript!!&@$%^&')).toEqual( expectedCounts ) }) xit('includes numbers', () => { const expectedCounts = new Map(Object.entries({ testing: 2, 1: 1, 2: 1 })) - expect(words.count('1 2 testing testing')).toEqual(expectedCounts) + expect(count('1 2 testing testing')).toEqual(expectedCounts) }) xit('normalizes to lower case', () => { const expectedCounts = new Map(Object.entries({ go: 3 })) - expect(words.count('go Go GO')).toEqual(expectedCounts) + expect(count('go Go GO')).toEqual(expectedCounts) }) xit('counts properly international characters', () => { const expectedCounts = new Map( Object.entries({ '¡hola!': 1, '¿qué': 1, 'tal?': 1, 'привет!': 1 }) ) - expect(words.count('¡Hola! ¿Qué tal? Привет!')).toEqual(expectedCounts) + expect(count('¡Hola! ¿Qué tal? Привет!')).toEqual(expectedCounts) }) xit('counts multiline', () => { const expectedCounts = new Map(Object.entries({ hello: 1, world: 1 })) - expect(words.count('hello\nworld')).toEqual(expectedCounts) + expect(count('hello\nworld')).toEqual(expectedCounts) }) xit('counts tabs', () => { const expectedCounts = new Map(Object.entries({ hello: 1, world: 1 })) - expect(words.count('hello\tworld')).toEqual(expectedCounts) + expect(count('hello\tworld')).toEqual(expectedCounts) }) xit('counts multiple spaces as one', () => { const expectedCounts = new Map(Object.entries({ hello: 1, world: 1 })) - expect(words.count('hello world')).toEqual(expectedCounts) + expect(count('hello world')).toEqual(expectedCounts) }) xit('does not count leading or trailing whitespace', () => { const expectedCounts = new Map( Object.entries({ introductory: 1, course: 1 }) ) - expect(words.count('\t\tIntroductory Course ')).toEqual(expectedCounts) + expect(count('\t\tIntroductory Course ')).toEqual(expectedCounts) }) xit('handles properties that exist on Object’s prototype', () => { @@ -89,8 +87,8 @@ describe('words()', () => { 'ok?': 1, }) ) - expect( - words.count('reserved words like constructor and toString ok?') - ).toEqual(expectedCounts) + expect(count('reserved words like constructor and toString ok?')).toEqual( + expectedCounts + ) }) }) diff --git a/exercises/practice/word-count/word-count.ts b/exercises/practice/word-count/word-count.ts index e69de29bb..bc4826e25 100644 --- a/exercises/practice/word-count/word-count.ts +++ b/exercises/practice/word-count/word-count.ts @@ -0,0 +1,3 @@ +export function count() { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/word-search/word-search.example.ts b/exercises/practice/word-search/.meta/proof.ci.ts similarity index 99% rename from exercises/practice/word-search/word-search.example.ts rename to exercises/practice/word-search/.meta/proof.ci.ts index be2ac1111..4cef416f9 100644 --- a/exercises/practice/word-search/word-search.example.ts +++ b/exercises/practice/word-search/.meta/proof.ci.ts @@ -3,7 +3,7 @@ interface Result { end: number[] } -export default class WordSearch { +export class WordSearch { private grid: string[] constructor(grid: string[]) { this.grid = grid diff --git a/exercises/practice/word-search/word-search.test.ts b/exercises/practice/word-search/word-search.test.ts index 3c9e55663..7901903e5 100644 --- a/exercises/practice/word-search/word-search.test.ts +++ b/exercises/practice/word-search/word-search.test.ts @@ -1,4 +1,4 @@ -import WordSearch from './word-search' +import { WordSearch } from './word-search' describe('single line grids', () => { test('Should accept an initial game grid', () => { diff --git a/exercises/practice/word-search/word-search.ts b/exercises/practice/word-search/word-search.ts index c526cc9e0..3817a8328 100644 --- a/exercises/practice/word-search/word-search.ts +++ b/exercises/practice/word-search/word-search.ts @@ -1,4 +1,8 @@ -export default class WordSearch { +export class WordSearch { + constructor() { + throw new Error('Remove this statement and implement this function') + } + public find(words: string[]) { throw new Error('Implement this function') } diff --git a/exercises/practice/wordy/.meta/proof.ci.ts b/exercises/practice/wordy/.meta/proof.ci.ts new file mode 100644 index 000000000..91d9f7c9a --- /dev/null +++ b/exercises/practice/wordy/.meta/proof.ci.ts @@ -0,0 +1,51 @@ +class ArgumentError {} + +type MathFunc = (a: number, b: number) => number + +const OPERATIONS: { [key: string]: MathFunc } = { + plus: (a: number, b: number) => { + return a + b + }, + minus: (a: number, b: number) => { + return a - b + }, + multiplied: (a: number, b: number) => { + return a * b + }, + divided: (a: number, b: number) => { + return a / b + }, +} + +export function answer(question: string): number { + const query = question + .replace(/by /g, '') + .replace('What is ', '') + .replace('?', '') + const array = query.split(' ') + + let subtotal = 0 + + if (array.length > 5 || array.length < 3) { + throw new ArgumentError() + } + + if (array.length >= 3) { + const a = array[0] + const b: MathFunc = OPERATIONS[array[1]] + const c = array[2] + if (b === undefined) { + throw new ArgumentError() + } + subtotal = b(+a, +c) + } + if (array.length === 5) { + const d: MathFunc = OPERATIONS[array[3]] + const e = array[4] + if (d === undefined) { + throw new ArgumentError() + } + subtotal = d(subtotal, +e) + } + return subtotal +} diff --git a/exercises/practice/wordy/wordy.example.ts b/exercises/practice/wordy/wordy.example.ts deleted file mode 100644 index c6ab9e731..000000000 --- a/exercises/practice/wordy/wordy.example.ts +++ /dev/null @@ -1,60 +0,0 @@ -class ArgumentError {} - -class WordProblem { - private readonly question: string - - constructor(question: string) { - this.question = question - } - - public answer(): number { - type MathFunc = (a: number, b: number) => number - const operans: { [key: string]: MathFunc } = { - plus: (a: number, b: number) => { - return a + b - }, - minus: (a: number, b: number) => { - return a - b - }, - multiplied: (a: number, b: number) => { - return a * b - }, - divided: (a: number, b: number) => { - return a / b - }, - } - - const querry = this.question - .replace(/by /g, '') - .replace('What is ', '') - .replace('?', '') - const array = querry.split(' ') - - let subtotal = 0 - - if (array.length > 5 || array.length < 3) { - throw new ArgumentError() - } - - if (array.length >= 3) { - const a = array[0] - const b: MathFunc = operans[array[1]] - const c = array[2] - if (b === undefined) { - throw new ArgumentError() - } - subtotal = b(+a, +c) - } - if (array.length === 5) { - const d: MathFunc = operans[array[3]] - const e = array[4] - if (d === undefined) { - throw new ArgumentError() - } - subtotal = d(subtotal, +e) - } - return subtotal - } -} - -export { ArgumentError, WordProblem } diff --git a/exercises/practice/wordy/wordy.test.ts b/exercises/practice/wordy/wordy.test.ts index c8c9f552c..df82b25b0 100644 --- a/exercises/practice/wordy/wordy.test.ts +++ b/exercises/practice/wordy/wordy.test.ts @@ -1,87 +1,103 @@ -import { WordProblem, ArgumentError } from './wordy' +import { answer } from './wordy' -describe('Word Problem', () => { - it('add 1', () => { - const question = 'What is 1 plus 1?' - expect(new WordProblem(question).answer()).toEqual(2) +describe('Wordy', () => { + test('just a number', () => { + expect(answer('What is 5?')).toEqual(5) }) - xit('add 2', () => { - const question = 'What is 53 plus 2?' - expect(new WordProblem(question).answer()).toEqual(55) + xtest('addition', () => { + expect(answer('What is 1 plus 1?')).toEqual(2) }) - xit('add negative numbers', () => { - const question = 'What is -1 plus -10?' - expect(new WordProblem(question).answer()).toEqual(-11) + xtest('more addition', () => { + expect(answer('What is 53 plus 2?')).toEqual(55) }) - xit('add more digits', () => { - const question = 'What is 123 plus 45678?' - expect(new WordProblem(question).answer()).toEqual(45801) + xtest('addition with negative numbers', () => { + expect(answer('What is -1 plus -10?')).toEqual(-11) }) - xit('subtract', () => { - const question = 'What is 4 minus -12?' - expect(new WordProblem(question).answer()).toEqual(16) + xtest('large addition', () => { + expect(answer('What is 123 plus 45678?')).toEqual(45801) }) - xit('multiply', () => { - const question = 'What is -3 multiplied by 25?' - expect(new WordProblem(question).answer()).toEqual(-75) + xtest('subtraction', () => { + expect(answer('What is 4 minus -12?')).toEqual(16) }) - xit('divide', () => { - const question = 'What is 33 divided by -3?' - expect(new WordProblem(question).answer()).toEqual(-11) + xtest('multiplication', () => { + expect(answer('What is -3 multiplied by 25?')).toEqual(-75) }) - xit('add twice', () => { - const question = 'What is 1 plus 1 plus 1?' - expect(new WordProblem(question).answer()).toEqual(3) + xtest('division', () => { + expect(answer('What is 33 divided by -3?')).toEqual(-11) }) - xit('add then subtract', () => { - const question = 'What is 1 plus 5 minus -2?' - expect(new WordProblem(question).answer()).toEqual(8) + xtest('multiple additions', () => { + expect(answer('What is 1 plus 1 plus 1?')).toEqual(3) }) - xit('subtract twice', () => { - const question = 'What is 20 minus 4 minus 13?' - expect(new WordProblem(question).answer()).toEqual(3) + xtest('addition and subtraction', () => { + expect(answer('What is 1 plus 5 minus -2?')).toEqual(8) }) - xit('subtract then add', () => { - const question = 'What is 17 minus 6 plus 3?' - expect(new WordProblem(question).answer()).toEqual(14) + xtest('multiple subtraction', () => { + expect(answer('What is 20 minus 4 minus 13?')).toEqual(3) }) - xit('multiply twice', () => { - const question = 'What is 2 multiplied by -2 multiplied by 3?' - expect(new WordProblem(question).answer()).toEqual(-12) + xtest('subtraction then addition', () => { + expect(answer('What is 17 minus 6 plus 3?')).toEqual(14) }) - xit('add then multiply', () => { - const question = 'What is -3 plus 7 multiplied by -2?' - expect(new WordProblem(question).answer()).toEqual(-8) + xtest('multiple multiplication', () => { + expect(answer('What is 2 multiplied by -2 multiplied by 3?')).toEqual(-12) }) - xit('divide twice', () => { - const question = 'What is -12 divided by 2 divided by -3?' - expect(new WordProblem(question).answer()).toEqual(2) + xtest('addition and multiplication', () => { + expect(answer('What is -3 plus 7 multiplied by -2?')).toEqual(-8) }) - xit('too advanced', () => { - const question = 'What is 53 cubed?' - const problem = new WordProblem(question) + xtest('multiple division', () => { + expect(answer('What is -12 divided by 2 divided by -3?')).toEqual(2) + }) + + xtest('unknown operation', () => { + expect(() => answer('What is 52 cubed?')).toThrow( + new Error('Unknown operation') + ) + }) + + xtest('Non math question', () => { + expect(() => answer('Who is the President of the United States?')).toThrow( + new Error('Unknown operation') + ) + }) + + xtest('reject problem missing an operand', () => { + expect(() => answer('What is 1 plus?')).toThrow(new Error('Syntax error')) + }) + + xtest('reject problem with no operands or operators', () => { + expect(() => answer('What is?')).toThrow(new Error('Syntax error')) + }) - expect(problem.answer.bind(problem)).toThrowError(ArgumentError) + xtest('reject two operations in a row', () => { + expect(() => answer('What is 1 plus plus 2?')).toThrow( + new Error('Syntax error') + ) }) - xit('irrelevant', () => { - const question = 'Who is the president of the United States?' - const problem = new WordProblem(question) + xtest('reject two numbers in a row', () => { + expect(() => answer('What is 1 plus 2 1?')).toThrow( + new Error('Syntax error') + ) + }) + + xtest('reject postfix notation', () => { + expect(() => answer('What is 1 2 plus?')).toThrow(new Error('Syntax error')) + }) - expect(problem.answer.bind(problem)).toThrowError(ArgumentError) + xtest('reject prefix notation', () => { + expect(() => answer('What is plus 1 2?')).toThrow(new Error('Syntax error')) }) }) diff --git a/exercises/practice/wordy/wordy.ts b/exercises/practice/wordy/wordy.ts index e69de29bb..04cd3a67d 100644 --- a/exercises/practice/wordy/wordy.ts +++ b/exercises/practice/wordy/wordy.ts @@ -0,0 +1,3 @@ +export const answer = () => { + throw new Error('Remove this statement and implement this function') +} From fcb06002eb4381eee2673a2272dc8f899fe15796 Mon Sep 17 00:00:00 2001 From: Derk-Jan Karrenbeld Date: Sat, 10 Apr 2021 06:12:17 +0200 Subject: [PATCH 2/2] Fix proofs --- .../practice/alphametics/.meta/proof.ci.ts | 6 +- exercises/practice/clock/.meta/proof.ci.ts | 2 +- .../collatz-conjecture/.meta/proof.ci.ts | 4 +- .../complex-numbers/.meta/proof.ci.ts | 2 +- .../practice/flatten-array/.meta/proof.ci.ts | 2 +- .../practice/grade-school/.meta/proof.ci.ts | 51 ++++------ .../grade-school/grade-school.test.ts | 16 ++- exercises/practice/grains/.meta/proof.ci.ts | 2 +- .../practice/hello-world/hello-world.test.ts | 2 +- exercises/practice/leap/.meta/proof.ci.ts | 2 +- .../{nth-prime.example.ts => proof.ci.ts} | 0 .../palindrome-products/.meta/proof.ci.ts | 98 ++++++++++++------- .../palindrome-products.ts | 12 +-- exercises/practice/pangram/.meta/proof.ci.ts | 4 +- .../practice/robot-name/.meta/proof.ci.ts | 2 +- .../practice/robot-name/robot-name.test.ts | 2 +- exercises/practice/robot-name/robot-name.ts | 2 +- .../.meta/proof.ci.ts | 4 +- exercises/practice/wordy/.meta/proof.ci.ts | 78 +++++++-------- 19 files changed, 142 insertions(+), 149 deletions(-) rename exercises/practice/nth-prime/.meta/{nth-prime.example.ts => proof.ci.ts} (100%) diff --git a/exercises/practice/alphametics/.meta/proof.ci.ts b/exercises/practice/alphametics/.meta/proof.ci.ts index 031da350c..fec11714e 100644 --- a/exercises/practice/alphametics/.meta/proof.ci.ts +++ b/exercises/practice/alphametics/.meta/proof.ci.ts @@ -89,9 +89,9 @@ function* generate(A: number[]): IterableIterator { while (i < n) { if (c[i] < i) { if (i % 2 === 0) { - this.swap(A, 0, i) + swap(A, 0, i) } else { - this.swap(A, c[i], i) + swap(A, c[i], i) } yield A c[i] += 1 @@ -118,7 +118,7 @@ function getNumberCombinations(arr: number[], size: number): number[][] { } return arr.reduce((acc: number[][], val: number, i: number) => { - const res: number[][] = this.getNumberCombinations( + const res: number[][] = getNumberCombinations( arr.slice(i + 1), size - 1 ).map((comb) => [val].concat(comb)) diff --git a/exercises/practice/clock/.meta/proof.ci.ts b/exercises/practice/clock/.meta/proof.ci.ts index 3bb0caad2..fd2349b15 100644 --- a/exercises/practice/clock/.meta/proof.ci.ts +++ b/exercises/practice/clock/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default class Clock { +export class Clock { private hour!: number private minute!: number diff --git a/exercises/practice/collatz-conjecture/.meta/proof.ci.ts b/exercises/practice/collatz-conjecture/.meta/proof.ci.ts index c13c9691e..b69fb41b6 100644 --- a/exercises/practice/collatz-conjecture/.meta/proof.ci.ts +++ b/exercises/practice/collatz-conjecture/.meta/proof.ci.ts @@ -9,8 +9,8 @@ function calculateStepsRecursively(n: number, count: number): number { if (n === 1) { return count } else if (n % 2 === 0) { - return this.calculateStepsRecursively(n / 2, ++count) + return calculateStepsRecursively(n / 2, ++count) } else { - return this.calculateStepsRecursively(n * 3 + 1, ++count) + return calculateStepsRecursively(n * 3 + 1, ++count) } } diff --git a/exercises/practice/complex-numbers/.meta/proof.ci.ts b/exercises/practice/complex-numbers/.meta/proof.ci.ts index 93ecd3e55..a289174cd 100644 --- a/exercises/practice/complex-numbers/.meta/proof.ci.ts +++ b/exercises/practice/complex-numbers/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export default class ComplexNumber { +export class ComplexNumber { public readonly real: number public readonly imag: number diff --git a/exercises/practice/flatten-array/.meta/proof.ci.ts b/exercises/practice/flatten-array/.meta/proof.ci.ts index fc272245b..464a2aa85 100644 --- a/exercises/practice/flatten-array/.meta/proof.ci.ts +++ b/exercises/practice/flatten-array/.meta/proof.ci.ts @@ -2,7 +2,7 @@ export function flatten>(arr: T): A[] { return arr .reduce( (acc: A[], el) => - Array.isArray(el) ? acc.concat(this.flatten(el)) : acc.concat(el), + Array.isArray(el) ? acc.concat(flatten(el)) : acc.concat(el), [] ) .filter((el: A) => el !== null && el !== undefined) diff --git a/exercises/practice/grade-school/.meta/proof.ci.ts b/exercises/practice/grade-school/.meta/proof.ci.ts index 9efcbe4ea..453ab7332 100644 --- a/exercises/practice/grade-school/.meta/proof.ci.ts +++ b/exercises/practice/grade-school/.meta/proof.ci.ts @@ -1,45 +1,34 @@ type Student = string type Grade = number -type StudentRooster = Map +type StudentRooster = Record type StudentGrades = Map - -class GradeSchool { - private studentGrades: StudentGrades +export class GradeSchool { + students: StudentGrades constructor() { - this.studentGrades = new Map() + this.students = new Map() } - private studentGradeEntries(): Array<[Student, Grade]> { - return Array.from(this.studentGrades.entries()) + add(student: Student, level: Grade) { + this.students.set(student, level) } - public studentRoster(): StudentRooster { - const grades: Grade[] = Array.from( - new Set(this.studentGrades.values()).values() - ).sort() - - const emptyStudentsRooster: StudentRooster = new Map() - - const gradesReducer = ( - rooster: StudentRooster, - grade: Grade - ): StudentRooster => - rooster.set(grade.toString(), this.studentsInGrade(grade)) - - return grades.reduce(gradesReducer, emptyStudentsRooster) + grade(level: Grade) { + return Array.from(this.students.entries()) + .filter(([, studentGrade]) => studentGrade === level) + .map(([student]) => student) + .sort() } - public addStudent(s: Student, g: Grade): void { - this.studentGrades.set(s, g) - } + roster(): StudentRooster { + const result: StudentRooster = {} - public studentsInGrade(g: Grade): Student[] { - return this.studentGradeEntries() - .filter(([_, sg]) => sg == g) - .map(([s, _]) => s) - .sort() + Array.from(this.students.entries()).forEach(([, studentGrade]) => { + if (!result[studentGrade]) { + result[studentGrade] = this.grade(studentGrade) + } + }) + + return result } } - -export default GradeSchool diff --git a/exercises/practice/grade-school/grade-school.test.ts b/exercises/practice/grade-school/grade-school.test.ts index 74ae0e3d6..1580714b3 100644 --- a/exercises/practice/grade-school/grade-school.test.ts +++ b/exercises/practice/grade-school/grade-school.test.ts @@ -1,13 +1,17 @@ import { GradeSchool } from './grade-school' describe('School', () => { + let school + + beforeEach(() => { + school = new GradeSchool() + }) + test('a new school has an empty roster', () => { - const school = new GradeSchool() expect(school.roster()).toEqual({}) }) xtest('adding a student adds them to the roster for the given grade', () => { - const school = new GradeSchool() school.add('Aimee', 2) const expectedDb = { 2: ['Aimee'] } @@ -15,7 +19,6 @@ describe('School', () => { }) xtest('adding more students to the same grade adds them to the roster', () => { - const school = new GradeSchool() school.add('Blair', 2) school.add('James', 2) school.add('Paul', 2) @@ -25,7 +28,6 @@ describe('School', () => { }) xtest('adding students to different grades adds them to the roster', () => { - const school = new GradeSchool() school.add('Chelsea', 3) school.add('Logan', 7) @@ -34,7 +36,6 @@ describe('School', () => { }) xtest('grade returns the students in that grade in alphabetical order', () => { - const school = new GradeSchool() school.add('Franklin', 5) school.add('Bradley', 5) school.add('Jeff', 1) @@ -44,12 +45,10 @@ describe('School', () => { }) xtest('grade returns an empty array if there are no students in that grade', () => { - const school = new GradeSchool() expect(school.grade(1)).toEqual([]) }) xtest('the students names in each grade in the roster are sorted', () => { - const school = new GradeSchool() school.add('Jennifer', 4) school.add('Kareem', 6) school.add('Christopher', 4) @@ -64,7 +63,6 @@ describe('School', () => { }) xtest('roster cannot be modified outside of module', () => { - const school = new GradeSchool() school.add('Aimee', 2) const roster = school.roster() roster[2].push('Oops.') @@ -73,7 +71,6 @@ describe('School', () => { }) xtest('roster cannot be modified outside of module using grade()', () => { - const school = new GradeSchool() school.add('Aimee', 2) school.grade(2).push('Oops.') const expectedDb = { 2: ['Aimee'] } @@ -81,7 +78,6 @@ describe('School', () => { }) xtest("a student can't be in two different grades", () => { - const school = new GradeSchool() school.add('Aimee', 2) school.add('Aimee', 1) diff --git a/exercises/practice/grains/.meta/proof.ci.ts b/exercises/practice/grains/.meta/proof.ci.ts index 26b402b8b..f9878a6bf 100644 --- a/exercises/practice/grains/.meta/proof.ci.ts +++ b/exercises/practice/grains/.meta/proof.ci.ts @@ -10,7 +10,7 @@ export function total(): number { let total = 0 for (let i = 1; i <= 64; i++) { - total += this.square(i) + total += square(i) } return total diff --git a/exercises/practice/hello-world/hello-world.test.ts b/exercises/practice/hello-world/hello-world.test.ts index 23bf860da..461e3ae0b 100644 --- a/exercises/practice/hello-world/hello-world.test.ts +++ b/exercises/practice/hello-world/hello-world.test.ts @@ -1,7 +1,7 @@ import { hello } from './hello-world' describe('Hello World', () => { - it('says hello world with no name', () => { + it('says hello world', () => { expect(hello()).toEqual('Hello, World!') }) }) diff --git a/exercises/practice/leap/.meta/proof.ci.ts b/exercises/practice/leap/.meta/proof.ci.ts index edb55157d..4989b240a 100644 --- a/exercises/practice/leap/.meta/proof.ci.ts +++ b/exercises/practice/leap/.meta/proof.ci.ts @@ -1,3 +1,3 @@ -export function isLeapYear(year: number): boolean { +export function isLeap(year: number): boolean { return (year % 100 !== 0 && year % 4 === 0) || year % 400 === 0 } diff --git a/exercises/practice/nth-prime/.meta/nth-prime.example.ts b/exercises/practice/nth-prime/.meta/proof.ci.ts similarity index 100% rename from exercises/practice/nth-prime/.meta/nth-prime.example.ts rename to exercises/practice/nth-prime/.meta/proof.ci.ts diff --git a/exercises/practice/palindrome-products/.meta/proof.ci.ts b/exercises/practice/palindrome-products/.meta/proof.ci.ts index 6feb85683..6eead0c62 100644 --- a/exercises/practice/palindrome-products/.meta/proof.ci.ts +++ b/exercises/practice/palindrome-products/.meta/proof.ci.ts @@ -8,48 +8,72 @@ interface Palindrome { factors: Array<[number, number]> } -interface Output { - largest: Palindrome - smallest: Palindrome +const reverseString = (str: string) => str.split('').reverse().join('') + +export function generate(params: Input): Palindromes { + if ((params.minFactor || 1) > params.maxFactor) { + throw new Error('min must be <= max') + } + return new Palindromes(params.maxFactor, params.minFactor || 1) } -export function generate({ maxFactor, minFactor = 1 }: Input): Output { - const factors = Array.from( - { length: maxFactor - minFactor + 1 }, - (_, k) => k + minFactor - ) - const products: Map> = new Map< - number, - Array<[number, number]> - >() - - let min = Infinity - let max = 0 - - factors.forEach((x: number, index: number) => { - factors.slice(index).forEach((y) => { - const product = x * y - if (isPalidrome(product)) { - const factorPair: [number, number] = [x, y] - const newFactors = products.get(product) || [] - newFactors.push(factorPair) - products.set(product, newFactors) - - min = Math.min(min, product) - max = Math.max(max, product) - } - }) - }) +class Palindrome { + constructor(factor1: number, factor2: number) { + this.value = factor1 * factor2 + this.factors = [[factor1, factor2].sort() as [number, number]] + } + + withFactors(factors) { + this.factors.push(factors.sort()) + this.factors = this.factors.sort() + return this + } - const largest: Palindrome = { value: max, factors: products.get(max) || [] } - const smallest: Palindrome = { value: min, factors: products.get(min) || [] } + valid() { + const s = `${this.value}` + return s === reverseString(s) + } - return { largest, smallest } + merge(other) { + other.factors.forEach((f) => this.factors.push(f)) + this.factors = this.factors.sort() + return this + } } -function isPalidrome(x: number): boolean { - const a = x.toString() - const b = [...a].reverse().join('') +export class Palindromes { + constructor(public maxFactor: number, public minFactor = 1) {} - return a === b + get largest() { + let best = new Palindrome(this.minFactor, this.minFactor) + for (let m = this.maxFactor; m >= this.minFactor; m -= 1) { + let p = null + for (let n = m; n >= this.minFactor && (!p || !p.valid()); n -= 1) { + p = new Palindrome(m, n) + if (p.valid()) { + if (best.value < p.value) { + best = p + } else if (best.value === p.value) { + best = p.merge(best) + } + } + } + } + if (best.valid()) { + return best + } + return { value: null, factors: [] } + } + + get smallest() { + for (let m = this.minFactor; m <= this.maxFactor; m += 1) { + for (let n = this.minFactor; n <= this.maxFactor; n += 1) { + const p = new Palindrome(m, n) + if (p.valid()) { + return p + } + } + } + return { value: null, factors: [] } + } } diff --git a/exercises/practice/palindrome-products/palindrome-products.ts b/exercises/practice/palindrome-products/palindrome-products.ts index 3ff5504c6..b7cf33f86 100644 --- a/exercises/practice/palindrome-products/palindrome-products.ts +++ b/exercises/practice/palindrome-products/palindrome-products.ts @@ -3,16 +3,6 @@ interface Input { minFactor?: number } -interface Palindrome { - value: number - factors: Array<[number, number]> -} - -interface Output { - largest: Palindrome - smallest: Palindrome -} - -export function generate(input: Input): Output { +export function generate(params: Input): unknown { throw new Error('Remove this statement and implement this function') } diff --git a/exercises/practice/pangram/.meta/proof.ci.ts b/exercises/practice/pangram/.meta/proof.ci.ts index e4b975a30..6a513d7a0 100644 --- a/exercises/practice/pangram/.meta/proof.ci.ts +++ b/exercises/practice/pangram/.meta/proof.ci.ts @@ -2,13 +2,13 @@ const aToZ = [...Array(26)].map((_, index) => { return String.fromCharCode(index + 65) }) -export function isPangram(): boolean { +export function isPangram(value: string): boolean { const myMap = new Map() aToZ.forEach((key: string) => { myMap.set(key.toLowerCase(), 0) }) - for (const each of this.value) { + for (const each of value) { const value = myMap.get(each) || 0 myMap.set(each.toLowerCase(), value + 1) } diff --git a/exercises/practice/robot-name/.meta/proof.ci.ts b/exercises/practice/robot-name/.meta/proof.ci.ts index 878f4a291..2fbbba422 100644 --- a/exercises/practice/robot-name/.meta/proof.ci.ts +++ b/exercises/practice/robot-name/.meta/proof.ci.ts @@ -48,7 +48,7 @@ class NameDatabase { const RobotsDB = new NameDatabase() -export default class Robot { +export class Robot { private _name!: string public get name(): string { diff --git a/exercises/practice/robot-name/robot-name.test.ts b/exercises/practice/robot-name/robot-name.test.ts index f73c5d751..73a38a58a 100644 --- a/exercises/practice/robot-name/robot-name.test.ts +++ b/exercises/practice/robot-name/robot-name.test.ts @@ -1,4 +1,4 @@ -import Robot from './robot-name' +import { Robot } from './robot-name' const areSequential = (name1: string, name2: string): boolean => { const alpha1 = name1.substr(0, 2) diff --git a/exercises/practice/robot-name/robot-name.ts b/exercises/practice/robot-name/robot-name.ts index 5ad478e12..b7396ccfd 100644 --- a/exercises/practice/robot-name/robot-name.ts +++ b/exercises/practice/robot-name/robot-name.ts @@ -1,4 +1,4 @@ -export default class Robot { +export class Robot { constructor() {} public get name(): string { diff --git a/exercises/practice/variable-length-quantity/.meta/proof.ci.ts b/exercises/practice/variable-length-quantity/.meta/proof.ci.ts index 67a0110ea..ecc4885f7 100644 --- a/exercises/practice/variable-length-quantity/.meta/proof.ci.ts +++ b/exercises/practice/variable-length-quantity/.meta/proof.ci.ts @@ -24,7 +24,7 @@ function decodeOne(buf: number[]): number { return val >>> 0 // convert to unsigned 32-bit } -function encode(data: number[]): number[] { +export function encode(data: number[]): number[] { let buf: number[] = [] for (let i = 0; i <= data.length - 1; i++) { @@ -33,7 +33,7 @@ function encode(data: number[]): number[] { return buf } -function decode(data: number[]): number[] { +export function decode(data: number[]): number[] { let start = 0 const vals = [] diff --git a/exercises/practice/wordy/.meta/proof.ci.ts b/exercises/practice/wordy/.meta/proof.ci.ts index 91d9f7c9a..6d70ebe1d 100644 --- a/exercises/practice/wordy/.meta/proof.ci.ts +++ b/exercises/practice/wordy/.meta/proof.ci.ts @@ -1,51 +1,45 @@ -class ArgumentError {} +const compute = ( + operand1: number, + operand2: number, + operation: string +): number => { + switch (operation) { + case 'plus': + return operand1 + operand2 + case 'minus': + return operand1 - operand2 + case 'divided by': + return operand1 / operand2 + case 'multiplied by': + return operand1 * operand2 + } +} -type MathFunc = (a: number, b: number) => number +export const answer = (question: string) => { + const operationsPattern = new RegExp(/plus|minus|divided by|multiplied by/g) + if ( + !operationsPattern.test(question) && + !/^What is ?-?\d*\?$/.test(question) + ) { + throw new Error('Unknown operation') + } -const OPERATIONS: { [key: string]: MathFunc } = { - plus: (a: number, b: number) => { - return a + b - }, - minus: (a: number, b: number) => { - return a - b - }, - multiplied: (a: number, b: number) => { - return a * b - }, - divided: (a: number, b: number) => { - return a / b - }, -} + const generalPattern = /^What is -?\d+( (plus|minus|multiplied by|divided by) -?\d+)*\?$/g + if (!generalPattern.test(question)) { + throw new Error('Syntax error') + } -export function answer(question: string): number { - const query = question - .replace(/by /g, '') - .replace('What is ', '') - .replace('?', '') - const array = query.split(' ') + const operations = question.match(operationsPattern) + const operands = question.match(/-?\d+/g) - let subtotal = 0 + let result = parseInt(operands[0]) - if (array.length > 5 || array.length < 3) { - throw new ArgumentError() + if (!operations) { + return result } - if (array.length >= 3) { - const a = array[0] - const b: MathFunc = OPERATIONS[array[1]] - const c = array[2] - if (b === undefined) { - throw new ArgumentError() - } - subtotal = b(+a, +c) - } - if (array.length === 5) { - const d: MathFunc = OPERATIONS[array[3]] - const e = array[4] - if (d === undefined) { - throw new ArgumentError() - } - subtotal = d(subtotal, +e) + for (let i = 0; i < operations.length; i++) { + result = compute(result, parseInt(operands[i + 1]), operations[i]) } - return subtotal + return result }