summary refs log tree commit diff stats
path: root/raku
diff options
context:
space:
mode:
authorAndinus <andinus@nand.sh>2021-08-11 15:26:15 +0530
committerAndinus <andinus@nand.sh>2021-08-11 15:26:15 +0530
commit321825828ac918bad28d0597a8616c6dc9802c3c (patch)
tree0b8e9cb1012197750eb58e972736319b2a6abac2 /raku
parent2979ef790ac5b8f58495e0dd08cafd6a3a2e30a5 (diff)
downloadexercism-321825828ac918bad28d0597a8616c6dc9802c3c.tar.gz
Add solved exercises
Diffstat (limited to 'raku')
-rw-r--r--raku/anagram/Anagram.rakumod8
-rw-r--r--raku/anagram/README.md31
-rw-r--r--raku/anagram/anagram.rakutest223
-rw-r--r--raku/bob/Bob.rakumod15
-rw-r--r--raku/bob/README.md39
-rw-r--r--raku/bob/bob.rakutest226
-rw-r--r--raku/clock/Clock.rakumod27
-rw-r--r--raku/clock/README.md30
-rw-r--r--raku/clock/clock.rakutest642
-rw-r--r--raku/hamming/Hamming.rakumod12
-rw-r--r--raku/hamming/README.md47
-rw-r--r--raku/hamming/hamming.rakutest122
-rw-r--r--raku/hello-world/HelloWorld.rakumod5
-rw-r--r--raku/hello-world/README.md38
-rw-r--r--raku/hello-world/hello-world.rakutest12
-rw-r--r--raku/leap/Leap.rakumod5
-rw-r--r--raku/leap/README.md47
-rw-r--r--raku/leap/leap.rakutest99
-rw-r--r--raku/luhn/Luhn.rakumod4
-rw-r--r--raku/luhn/README.md88
-rw-r--r--raku/luhn/luhn.rakutest165
-rw-r--r--raku/nucleotide-count/NucleotideCount.rakumod6
-rw-r--r--raku/nucleotide-count/README.md36
-rw-r--r--raku/nucleotide-count/nucleotide-count.rakutest95
-rw-r--r--raku/pangram/Pangram.rakumod5
-rw-r--r--raku/pangram/README.md32
-rw-r--r--raku/pangram/pangram.rakutest101
-rw-r--r--raku/phone-number/Phone.rakumod16
-rw-r--r--raku/phone-number/README.md52
-rw-r--r--raku/phone-number/phone-number.rakutest200
-rw-r--r--raku/raindrops/README.md39
-rw-r--r--raku/raindrops/Raindrops.rakumod9
-rw-r--r--raku/raindrops/raindrops.rakutest165
-rw-r--r--raku/two-fer/README.md49
-rw-r--r--raku/two-fer/TwoFer.rakumod5
-rw-r--r--raku/two-fer/two-fer.rakutest49
-rw-r--r--raku/word-count/README.md54
-rw-r--r--raku/word-count/WordCount.rakumod5
-rw-r--r--raku/word-count/word-count.rakutest184
39 files changed, 2987 insertions, 0 deletions
diff --git a/raku/anagram/Anagram.rakumod b/raku/anagram/Anagram.rakumod
new file mode 100644
index 0000000..5f67e65
--- /dev/null
+++ b/raku/anagram/Anagram.rakumod
@@ -0,0 +1,8 @@
+unit module Anagram;
+
+sub match-anagrams (Str :$subject!, :@candidates!) is export {
+    gather for @candidates {
+        next if .fc eq $subject.fc;
+        take $_ if .fc.comb.Bag eqv $subject.fc.comb.Bag;
+    }
+}
diff --git a/raku/anagram/README.md b/raku/anagram/README.md
new file mode 100644
index 0000000..ca11115
--- /dev/null
+++ b/raku/anagram/README.md
@@ -0,0 +1,31 @@
+# Anagram
+
+An anagram is a rearrangement of letters to form a new word.
+Given a word and a list of candidates, select the sublist of anagrams of the given word.
+
+Given `"listen"` and a list of candidates like `"enlists" "google"
+"inlets" "banana"` the program should return a list containing
+`"inlets"`.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+Inspired by the Extreme Startup game [https://github.com/rchatley/extreme_startup](https://github.com/rchatley/extreme_startup)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/anagram/anagram.rakutest b/raku/anagram/anagram.rakutest
new file mode 100644
index 0000000..6aa62c6
--- /dev/null
+++ b/raku/anagram/anagram.rakutest
@@ -0,0 +1,223 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Anagram;
+plan 14;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  cmp-ok match-anagrams( |%case<input>:p ),
+    '~~', %case<expected>.Set, %case<description>;
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "no matches",
+    "expected": [
+    ],
+    "input": {
+      "candidates": [
+        "hello",
+        "world",
+        "zombies",
+        "pants"
+      ],
+      "subject": "diaper"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects two anagrams",
+    "expected": [
+      "stream",
+      "maters"
+    ],
+    "input": {
+      "candidates": [
+        "stream",
+        "pigeon",
+        "maters"
+      ],
+      "subject": "master"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "does not detect anagram subsets",
+    "expected": [
+    ],
+    "input": {
+      "candidates": [
+        "dog",
+        "goody"
+      ],
+      "subject": "good"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects anagram",
+    "expected": [
+      "inlets"
+    ],
+    "input": {
+      "candidates": [
+        "enlists",
+        "google",
+        "inlets",
+        "banana"
+      ],
+      "subject": "listen"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects three anagrams",
+    "expected": [
+      "gallery",
+      "regally",
+      "largely"
+    ],
+    "input": {
+      "candidates": [
+        "gallery",
+        "ballerina",
+        "regally",
+        "clergy",
+        "largely",
+        "leading"
+      ],
+      "subject": "allergy"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects multiple anagrams with different case",
+    "expected": [
+      "Eons",
+      "ONES"
+    ],
+    "input": {
+      "candidates": [
+        "Eons",
+        "ONES"
+      ],
+      "subject": "nose"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "does not detect non-anagrams with identical checksum",
+    "expected": [
+    ],
+    "input": {
+      "candidates": [
+        "last"
+      ],
+      "subject": "mass"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects anagrams case-insensitively",
+    "expected": [
+      "Carthorse"
+    ],
+    "input": {
+      "candidates": [
+        "cashregister",
+        "Carthorse",
+        "radishes"
+      ],
+      "subject": "Orchestra"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects anagrams using case-insensitive subject",
+    "expected": [
+      "carthorse"
+    ],
+    "input": {
+      "candidates": [
+        "cashregister",
+        "carthorse",
+        "radishes"
+      ],
+      "subject": "Orchestra"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "detects anagrams using case-insensitive possible matches",
+    "expected": [
+      "Carthorse"
+    ],
+    "input": {
+      "candidates": [
+        "cashregister",
+        "Carthorse",
+        "radishes"
+      ],
+      "subject": "orchestra"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "does not detect an anagram if the original word is repeated",
+    "expected": [
+    ],
+    "input": {
+      "candidates": [
+        "go Go GO"
+      ],
+      "subject": "go"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "anagrams must use all letters exactly once",
+    "expected": [
+    ],
+    "input": {
+      "candidates": [
+        "patter"
+      ],
+      "subject": "tapper"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "words are not anagrams of themselves (case-insensitive)",
+    "expected": [
+    ],
+    "input": {
+      "candidates": [
+        "BANANA",
+        "Banana",
+        "banana"
+      ],
+      "subject": "BANANA"
+    },
+    "property": "findAnagrams"
+  },
+  {
+    "description": "words other than themselves can be anagrams",
+    "expected": [
+      "Silent"
+    ],
+    "input": {
+      "candidates": [
+        "Listen",
+        "Silent",
+        "LISTEN"
+      ],
+      "subject": "LISTEN"
+    },
+    "property": "findAnagrams"
+  }
+]
+=end code
diff --git a/raku/bob/Bob.rakumod b/raku/bob/Bob.rakumod
new file mode 100644
index 0000000..ab6fa66
--- /dev/null
+++ b/raku/bob/Bob.rakumod
@@ -0,0 +1,15 @@
+unit class Bob;
+
+method hey (Str $msg is copy) {
+    $msg .= trim;
+
+    given $msg.trim {
+        when .chars == 0 { "Fine. Be that way!" }
+        when $_ eq .uc && $_ ~~ /<[a..zA..Z]>/ {
+            return "Calm down, I know what I'm doing!" if $_.ends-with("?");
+            "Whoa, chill out!";
+        }
+        when .ends-with("?") { "Sure." }
+        default { "Whatever." }
+    }
+}
diff --git a/raku/bob/README.md b/raku/bob/README.md
new file mode 100644
index 0000000..01800c5
--- /dev/null
+++ b/raku/bob/README.md
@@ -0,0 +1,39 @@
+# Bob
+
+Bob is a lackadaisical teenager. In conversation, his responses are very limited.
+
+Bob answers 'Sure.' if you ask him a question, such as "How are you?".
+
+He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals).
+
+He answers 'Calm down, I know what I'm doing!' if you yell a question at him.
+
+He says 'Fine. Be that way!' if you address him without actually saying
+anything.
+
+He answers 'Whatever.' to anything else.
+
+Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial. [http://pine.fm/LearnToProgram/?Chapter=06](http://pine.fm/LearnToProgram/?Chapter=06)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/bob/bob.rakutest b/raku/bob/bob.rakutest
new file mode 100644
index 0000000..a540719
--- /dev/null
+++ b/raku/bob/bob.rakutest
@@ -0,0 +1,226 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname; #`[Look for the module inside the same directory as this test file.]
+use Bob;
+plan 26; #`[This is how many tests we expect to run.]
+
+#`[Check that the class 'Bob' can use all of the methods
+needed in the tests (only 'hey' for this one).]
+subtest 'Class methods', {
+  for <hey> -> $method {
+    can-ok Bob, $method;
+  }
+} or bail-out 'Cannot run expected method(s).';
+
+my @test-cases = from-json($=pod.pop.contents).List;
+# Go through the cases and check that Bob gives us the correct responses.
+for @test-cases -> %case {
+  is Bob.hey(%case<input><heyBob>), |%case<expected description>;
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "stating something",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "Tom-ay-to, tom-aaaah-to."
+    },
+    "property": "response"
+  },
+  {
+    "description": "shouting",
+    "expected": "Whoa, chill out!",
+    "input": {
+      "heyBob": "WATCH OUT!"
+    },
+    "property": "response"
+  },
+  {
+    "description": "shouting gibberish",
+    "expected": "Whoa, chill out!",
+    "input": {
+      "heyBob": "FCECDFCAAB"
+    },
+    "property": "response"
+  },
+  {
+    "description": "asking a question",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": "Does this cryogenic chamber make me look fat?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "asking a numeric question",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": "You are, what, like 15?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "asking gibberish",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": "fffbbcbeab?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "talking forcefully",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "Hi there!"
+    },
+    "property": "response"
+  },
+  {
+    "description": "using acronyms in regular speech",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "It's OK if you don't want to go work for NASA."
+    },
+    "property": "response"
+  },
+  {
+    "description": "forceful question",
+    "expected": "Calm down, I know what I'm doing!",
+    "input": {
+      "heyBob": "WHAT'S GOING ON?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "shouting numbers",
+    "expected": "Whoa, chill out!",
+    "input": {
+      "heyBob": "1, 2, 3 GO!"
+    },
+    "property": "response"
+  },
+  {
+    "description": "no letters",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "1, 2, 3"
+    },
+    "property": "response"
+  },
+  {
+    "description": "question with no letters",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": "4?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "shouting with special characters",
+    "expected": "Whoa, chill out!",
+    "input": {
+      "heyBob": "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"
+    },
+    "property": "response"
+  },
+  {
+    "description": "shouting with no exclamation mark",
+    "expected": "Whoa, chill out!",
+    "input": {
+      "heyBob": "I HATE THE DENTIST"
+    },
+    "property": "response"
+  },
+  {
+    "description": "statement containing question mark",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "Ending with ? means a question."
+    },
+    "property": "response"
+  },
+  {
+    "description": "non-letters with question",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": ":) ?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "prattling on",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": "Wait! Hang on. Are you going to be OK?"
+    },
+    "property": "response"
+  },
+  {
+    "description": "silence",
+    "expected": "Fine. Be that way!",
+    "input": {
+      "heyBob": ""
+    },
+    "property": "response"
+  },
+  {
+    "description": "prolonged silence",
+    "expected": "Fine. Be that way!",
+    "input": {
+      "heyBob": "          "
+    },
+    "property": "response"
+  },
+  {
+    "description": "alternate silence",
+    "expected": "Fine. Be that way!",
+    "input": {
+      "heyBob": "\t\t\t\t\t\t\t\t\t\t"
+    },
+    "property": "response"
+  },
+  {
+    "description": "multiple line question",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "\nDoes this cryogenic chamber make me look fat?\nNo."
+    },
+    "property": "response"
+  },
+  {
+    "description": "starting with whitespace",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "         hmmmmmmm..."
+    },
+    "property": "response"
+  },
+  {
+    "description": "ending with whitespace",
+    "expected": "Sure.",
+    "input": {
+      "heyBob": "Okay if like my  spacebar  quite a bit?   "
+    },
+    "property": "response"
+  },
+  {
+    "description": "other whitespace",
+    "expected": "Fine. Be that way!",
+    "input": {
+      "heyBob": "\n\r \t"
+    },
+    "property": "response"
+  },
+  {
+    "description": "non-question ending with whitespace",
+    "expected": "Whatever.",
+    "input": {
+      "heyBob": "This is a statement ending with whitespace      "
+    },
+    "property": "response"
+  }
+]
+=end code
diff --git a/raku/clock/Clock.rakumod b/raku/clock/Clock.rakumod
new file mode 100644
index 0000000..cf2b5d4
--- /dev/null
+++ b/raku/clock/Clock.rakumod
@@ -0,0 +1,27 @@
+unit class Clock;
+
+has Int $.hour;
+has Int $.minute;
+
+submethod TWEAK() { self.wrap-time; }
+
+method time { sprintf "%02d:%02d", $!hour, $!minute; }
+
+method add-minutes(UInt $amount --> Clock) {
+    $!hour += $amount div 60;
+    $!minute += $amount % 60;
+    self.wrap-time;
+}
+
+method subtract-minutes(UInt $amount --> Clock) {
+    $!hour -= $amount div 60;
+    $!minute -= $amount % 60;
+    self.wrap-time;
+}
+
+method wrap-time(--> Clock) {
+    $!hour += $!minute div 60;
+    $!minute %= 60;
+    $!hour %= 24;
+    return self;
+}
diff --git a/raku/clock/README.md b/raku/clock/README.md
new file mode 100644
index 0000000..57a61aa
--- /dev/null
+++ b/raku/clock/README.md
@@ -0,0 +1,30 @@
+# Clock
+
+Implement a clock that handles times without dates.
+
+You should be able to add and subtract minutes to it.
+
+Two clocks that represent the same time should be equal to each other.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+Pairing session with Erin Drummond [https://twitter.com/ebdrummond](https://twitter.com/ebdrummond)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/clock/clock.rakutest b/raku/clock/clock.rakutest
new file mode 100644
index 0000000..ee57c0e
--- /dev/null
+++ b/raku/clock/clock.rakutest
@@ -0,0 +1,642 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Clock;
+plan 55;
+
+subtest 'Class methods', {
+  for <time add-minutes subtract-minutes> -> $method {
+    can-ok Clock, $method;
+  }
+} or bail-out 'Cannot run expected method(s).';
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  given %case<property> {
+
+    when 'create' {
+      is Clock.new( |%(.<input><hour minute>:p) ).time,
+        |.<expected description> given %case;
+    }
+
+    when <add subtract>.any {
+      given %case {
+        my $clock = Clock.new: |%(.<input><hour minute>:p);
+        $clock.add-minutes: .<input><value>
+          if .<property> eq 'add';
+        $clock.subtract-minutes: .<input><value>
+          if .<property> eq 'subtract';
+        is $clock.time, |.<expected description>;
+      }
+    }
+
+    when 'equal' {
+      is-deeply ([eq] gather {
+        take Clock.new( |%(.<hour minute>:p) ).time
+          for %case<input><clock1 clock2>;
+      }), |%case<expected description>;
+    }
+
+  }
+}
+
+is Clock.new(:0hour,:0minute).add-minutes(65).?time,
+  '01:05', 'add-minutes method can be chained';
+
+is Clock.new(:0hour,:0minute).subtract-minutes(65).?time,
+  '22:55', 'subtract-minutes method can be chained';
+
+CATCH {
+  when X::StubCode {
+    flunk 'Caught stub';
+    note "{.message} (does the sub/method contain '!!!'?)\n"
+      ~ .backtrace.concise;
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "Create a new clock with an initial time: on the hour",
+    "expected": "08:00",
+    "input": {
+      "hour": 8,
+      "minute": 0
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: past the hour",
+    "expected": "11:09",
+    "input": {
+      "hour": 11,
+      "minute": 9
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: midnight is zero hours",
+    "expected": "00:00",
+    "input": {
+      "hour": 24,
+      "minute": 0
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: hour rolls over",
+    "expected": "01:00",
+    "input": {
+      "hour": 25,
+      "minute": 0
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: hour rolls over continuously",
+    "expected": "04:00",
+    "input": {
+      "hour": 100,
+      "minute": 0
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: sixty minutes is next hour",
+    "expected": "02:00",
+    "input": {
+      "hour": 1,
+      "minute": 60
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: minutes roll over",
+    "expected": "02:40",
+    "input": {
+      "hour": 0,
+      "minute": 160
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: minutes roll over continuously",
+    "expected": "04:43",
+    "input": {
+      "hour": 0,
+      "minute": 1723
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: hour and minutes roll over",
+    "expected": "03:40",
+    "input": {
+      "hour": 25,
+      "minute": 160
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: hour and minutes roll over continuously",
+    "expected": "11:01",
+    "input": {
+      "hour": 201,
+      "minute": 3001
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: hour and minutes roll over to exactly midnight",
+    "expected": "00:00",
+    "input": {
+      "hour": 72,
+      "minute": 8640
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative hour",
+    "expected": "23:15",
+    "input": {
+      "hour": -1,
+      "minute": 15
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative hour rolls over",
+    "expected": "23:00",
+    "input": {
+      "hour": -25,
+      "minute": 0
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative hour rolls over continuously",
+    "expected": "05:00",
+    "input": {
+      "hour": -91,
+      "minute": 0
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative minutes",
+    "expected": "00:20",
+    "input": {
+      "hour": 1,
+      "minute": -40
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative minutes roll over",
+    "expected": "22:20",
+    "input": {
+      "hour": 1,
+      "minute": -160
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative minutes roll over continuously",
+    "expected": "16:40",
+    "input": {
+      "hour": 1,
+      "minute": -4820
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative sixty minutes is previous hour",
+    "expected": "01:00",
+    "input": {
+      "hour": 2,
+      "minute": -60
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative hour and minutes both roll over",
+    "expected": "20:20",
+    "input": {
+      "hour": -25,
+      "minute": -160
+    },
+    "property": "create"
+  },
+  {
+    "description": "Create a new clock with an initial time: negative hour and minutes both roll over continuously",
+    "expected": "22:10",
+    "input": {
+      "hour": -121,
+      "minute": -5810
+    },
+    "property": "create"
+  },
+  {
+    "description": "Add minutes: add minutes",
+    "expected": "10:03",
+    "input": {
+      "hour": 10,
+      "minute": 0,
+      "value": 3
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add no minutes",
+    "expected": "06:41",
+    "input": {
+      "hour": 6,
+      "minute": 41,
+      "value": 0
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add to next hour",
+    "expected": "01:25",
+    "input": {
+      "hour": 0,
+      "minute": 45,
+      "value": 40
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add more than one hour",
+    "expected": "11:01",
+    "input": {
+      "hour": 10,
+      "minute": 0,
+      "value": 61
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add more than two hours with carry",
+    "expected": "03:25",
+    "input": {
+      "hour": 0,
+      "minute": 45,
+      "value": 160
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add across midnight",
+    "expected": "00:01",
+    "input": {
+      "hour": 23,
+      "minute": 59,
+      "value": 2
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add more than one day (1500 min = 25 hrs)",
+    "expected": "06:32",
+    "input": {
+      "hour": 5,
+      "minute": 32,
+      "value": 1500
+    },
+    "property": "add"
+  },
+  {
+    "description": "Add minutes: add more than two days",
+    "expected": "11:21",
+    "input": {
+      "hour": 1,
+      "minute": 1,
+      "value": 3500
+    },
+    "property": "add"
+  },
+  {
+    "description": "Subtract minutes: subtract minutes",
+    "expected": "10:00",
+    "input": {
+      "hour": 10,
+      "minute": 3,
+      "value": 3
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract to previous hour",
+    "expected": "09:33",
+    "input": {
+      "hour": 10,
+      "minute": 3,
+      "value": 30
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract more than an hour",
+    "expected": "08:53",
+    "input": {
+      "hour": 10,
+      "minute": 3,
+      "value": 70
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract across midnight",
+    "expected": "23:59",
+    "input": {
+      "hour": 0,
+      "minute": 3,
+      "value": 4
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract more than two hours",
+    "expected": "21:20",
+    "input": {
+      "hour": 0,
+      "minute": 0,
+      "value": 160
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract more than two hours with borrow",
+    "expected": "03:35",
+    "input": {
+      "hour": 6,
+      "minute": 15,
+      "value": 160
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract more than one day (1500 min = 25 hrs)",
+    "expected": "04:32",
+    "input": {
+      "hour": 5,
+      "minute": 32,
+      "value": 1500
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Subtract minutes: subtract more than two days",
+    "expected": "00:20",
+    "input": {
+      "hour": 2,
+      "minute": 20,
+      "value": 3000
+    },
+    "property": "subtract"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with same time",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 15,
+        "minute": 37
+      },
+      "clock2": {
+        "hour": 15,
+        "minute": 37
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks a minute apart",
+    "expected": false,
+    "input": {
+      "clock1": {
+        "hour": 15,
+        "minute": 36
+      },
+      "clock2": {
+        "hour": 15,
+        "minute": 37
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks an hour apart",
+    "expected": false,
+    "input": {
+      "clock1": {
+        "hour": 14,
+        "minute": 37
+      },
+      "clock2": {
+        "hour": 15,
+        "minute": 37
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with hour overflow",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 10,
+        "minute": 37
+      },
+      "clock2": {
+        "hour": 34,
+        "minute": 37
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with hour overflow by several days",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 3,
+        "minute": 11
+      },
+      "clock2": {
+        "hour": 99,
+        "minute": 11
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative hour",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 22,
+        "minute": 40
+      },
+      "clock2": {
+        "hour": -2,
+        "minute": 40
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative hour that wraps",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 17,
+        "minute": 3
+      },
+      "clock2": {
+        "hour": -31,
+        "minute": 3
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative hour that wraps multiple times",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 13,
+        "minute": 49
+      },
+      "clock2": {
+        "hour": -83,
+        "minute": 49
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with minute overflow",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 0,
+        "minute": 1
+      },
+      "clock2": {
+        "hour": 0,
+        "minute": 1441
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with minute overflow by several days",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 2,
+        "minute": 2
+      },
+      "clock2": {
+        "hour": 2,
+        "minute": 4322
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative minute",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 2,
+        "minute": 40
+      },
+      "clock2": {
+        "hour": 3,
+        "minute": -20
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative minute that wraps",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 4,
+        "minute": 10
+      },
+      "clock2": {
+        "hour": 5,
+        "minute": -1490
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative minute that wraps multiple times",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 6,
+        "minute": 15
+      },
+      "clock2": {
+        "hour": 6,
+        "minute": -4305
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative hours and minutes",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 7,
+        "minute": 32
+      },
+      "clock2": {
+        "hour": -12,
+        "minute": -268
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: clocks with negative hours and minutes that wrap",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 18,
+        "minute": 7
+      },
+      "clock2": {
+        "hour": -54,
+        "minute": -11513
+      }
+    },
+    "property": "equal"
+  },
+  {
+    "description": "Compare two clocks for equality: full clock and zeroed clock",
+    "expected": true,
+    "input": {
+      "clock1": {
+        "hour": 24,
+        "minute": 0
+      },
+      "clock2": {
+        "hour": 0,
+        "minute": 0
+      }
+    },
+    "property": "equal"
+  }
+]
+=end code
diff --git a/raku/hamming/Hamming.rakumod b/raku/hamming/Hamming.rakumod
new file mode 100644
index 0000000..075b07c
--- /dev/null
+++ b/raku/hamming/Hamming.rakumod
@@ -0,0 +1,12 @@
+unit module Hamming;
+
+subset DNA of Str where * !~~ /<-[ACGT]>/;
+sub hamming-distance(
+    DNA $strand1, DNA $strand2 where $strand1.chars == $strand2.chars --> Int
+) is export {
+    my Int $dist = 0;
+    for ^$strand1.chars -> $idx {
+        $dist++ unless $strand1.comb[$idx] eq $strand2.comb[$idx];
+    }
+    return $dist;
+}
diff --git a/raku/hamming/README.md b/raku/hamming/README.md
new file mode 100644
index 0000000..b4f28b6
--- /dev/null
+++ b/raku/hamming/README.md
@@ -0,0 +1,47 @@
+# Hamming
+
+Calculate the Hamming Distance between two DNA strands.
+
+Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime!
+
+When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. This is known as the "Hamming Distance".
+
+We read DNA using the letters C,A,G and T. Two strands might look like this:
+
+    GAGCCTACTAACGGGAT
+    CATCGTAATGACGGCCT
+    ^ ^ ^  ^ ^    ^^
+
+They have 7 differences, and therefore the Hamming Distance is 7.
+
+The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :)
+
+# Implementation notes
+
+The Hamming distance is only defined for sequences of equal length, so
+an attempt to calculate it between sequences of different lengths should
+not work. The general handling of this situation (e.g., raising an
+exception vs returning a special value) may differ between languages.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+The Calculating Point Mutations problem at Rosalind [http://rosalind.info/problems/hamm/](http://rosalind.info/problems/hamm/)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/hamming/hamming.rakutest b/raku/hamming/hamming.rakutest
new file mode 100644
index 0000000..1c5cfb3
--- /dev/null
+++ b/raku/hamming/hamming.rakutest
@@ -0,0 +1,122 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Hamming;
+plan 9;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  given %case<expected> {
+    when .<error>.so {
+      throws-like
+        { hamming-distance |%case<input><strand1 strand2> },
+        Exception,
+        message => /
+          'left and right strands must be of equal length'
+          || 'Constraint type check failed in binding to parameter'
+        /,
+        %case<description>;
+    }
+
+    default {
+      is hamming-distance(|%case<input><strand1 strand2>),
+        |%case<expected description>;
+    }
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "empty strands",
+    "expected": 0,
+    "input": {
+      "strand1": "",
+      "strand2": ""
+    },
+    "property": "distance"
+  },
+  {
+    "description": "single letter identical strands",
+    "expected": 0,
+    "input": {
+      "strand1": "A",
+      "strand2": "A"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "single letter different strands",
+    "expected": 1,
+    "input": {
+      "strand1": "G",
+      "strand2": "T"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "long identical strands",
+    "expected": 0,
+    "input": {
+      "strand1": "GGACTGAAATCTG",
+      "strand2": "GGACTGAAATCTG"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "long different strands",
+    "expected": 9,
+    "input": {
+      "strand1": "GGACGGATTCTG",
+      "strand2": "AGGACGGATTCT"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "disallow first strand longer",
+    "expected": {
+      "error": "left and right strands must be of equal length"
+    },
+    "input": {
+      "strand1": "AATG",
+      "strand2": "AAA"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "disallow second strand longer",
+    "expected": {
+      "error": "left and right strands must be of equal length"
+    },
+    "input": {
+      "strand1": "ATA",
+      "strand2": "AGTG"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "disallow left empty strand",
+    "expected": {
+      "error": "left strand must not be empty"
+    },
+    "input": {
+      "strand1": "",
+      "strand2": "G"
+    },
+    "property": "distance"
+  },
+  {
+    "description": "disallow right empty strand",
+    "expected": {
+      "error": "right strand must not be empty"
+    },
+    "input": {
+      "strand1": "G",
+      "strand2": ""
+    },
+    "property": "distance"
+  }
+]
+=end code
diff --git a/raku/hello-world/HelloWorld.rakumod b/raku/hello-world/HelloWorld.rakumod
new file mode 100644
index 0000000..a914d42
--- /dev/null
+++ b/raku/hello-world/HelloWorld.rakumod
@@ -0,0 +1,5 @@
+unit module HelloWorld;
+
+sub hello is export {
+    return "Hello, World!";
+}
diff --git a/raku/hello-world/README.md b/raku/hello-world/README.md
new file mode 100644
index 0000000..f458433
--- /dev/null
+++ b/raku/hello-world/README.md
@@ -0,0 +1,38 @@
+# Hello World
+
+The classical introductory exercise. Just say "Hello, World!".
+
+["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is
+the traditional first program for beginning programming in a new language
+or environment.
+
+The objectives are simple:
+
+- Write a function that returns the string "Hello, World!".
+- Run the test suite and make sure that it succeeds.
+- Submit your solution and check it at the website.
+
+If everything goes well, you will be ready to fetch your first real exercise.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+This is an exercise to introduce users to using Exercism [http://en.wikipedia.org/wiki/%22Hello,_world!%22_program](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/hello-world/hello-world.rakutest b/raku/hello-world/hello-world.rakutest
new file mode 100644
index 0000000..b0c9c35
--- /dev/null
+++ b/raku/hello-world/hello-world.rakutest
@@ -0,0 +1,12 @@
+#!/usr/bin/env raku
+use Test;
+use lib $?FILE.IO.dirname; #`[Look for the module inside the same directory as this test file.]
+use HelloWorld;
+plan 1; #`[This is how many tests we expect to run.]
+
+# Run the 'is' subroutine from the 'Test' module, with three arguments.
+is(
+  hello,           # Run the 'hello' subroutine, which is imported from your module.
+  'Hello, World!', # The expected result to compare with 'hello'.
+  'Say Hi!'        # The test description.
+);
diff --git a/raku/leap/Leap.rakumod b/raku/leap/Leap.rakumod
new file mode 100644
index 0000000..0df3183
--- /dev/null
+++ b/raku/leap/Leap.rakumod
@@ -0,0 +1,5 @@
+unit module Leap;
+
+sub is-leap-year ($year) is export {
+    $year %% 4 && ($year !%% 100 || $year %% 400);
+}
diff --git a/raku/leap/README.md b/raku/leap/README.md
new file mode 100644
index 0000000..3fce837
--- /dev/null
+++ b/raku/leap/README.md
@@ -0,0 +1,47 @@
+# Leap
+
+Given a year, report if it is a leap year.
+
+The tricky thing here is that a leap year in the Gregorian calendar occurs:
+
+```text
+on every year that is evenly divisible by 4
+  except every year that is evenly divisible by 100
+    unless the year is also evenly divisible by 400
+```
+
+For example, 1997 is not a leap year, but 1996 is.  1900 is not a leap
+year, but 2000 is.
+
+## Notes
+
+Though our exercise adopts some very simple rules, there is more to
+learn!
+
+For a delightful, four minute explanation of the whole leap year
+phenomenon, go watch [this youtube video][video].
+
+[video]: http://www.youtube.com/watch?v=xX96xng7sAE
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+JavaRanch Cattle Drive, exercise 3 [http://www.javaranch.com/leap.jsp](http://www.javaranch.com/leap.jsp)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/leap/leap.rakutest b/raku/leap/leap.rakutest
new file mode 100644
index 0000000..a9ab7bb
--- /dev/null
+++ b/raku/leap/leap.rakutest
@@ -0,0 +1,99 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Leap;
+plan 9;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for Date, DateTime {
+  .^method_table<is-leap-year>.wrap: sub (|) {
+    bail-out 'Built-in `is-leap-year` method is not allowed for this exercise.';
+  };
+}
+
+for @test-cases -> %case {
+  subtest %case<description>, {
+    plan 2;
+    isa-ok ( my $result := is-leap-year %case<input><year> ), Bool;
+    is-deeply $result, %case<expected>, 'Result matches expected';
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "year not divisible by 4 in common year",
+    "expected": false,
+    "input": {
+      "year": 2015
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 2, not divisible by 4 in common year",
+    "expected": false,
+    "input": {
+      "year": 1970
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 4, not divisible by 100 in leap year",
+    "expected": true,
+    "input": {
+      "year": 1996
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 4 and 5 is still a leap year",
+    "expected": true,
+    "input": {
+      "year": 1960
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 100, not divisible by 400 in common year",
+    "expected": false,
+    "input": {
+      "year": 2100
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 100 but not by 3 is still not a leap year",
+    "expected": false,
+    "input": {
+      "year": 1900
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 400 in leap year",
+    "expected": true,
+    "input": {
+      "year": 2000
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 400 but not by 125 is still a leap year",
+    "expected": true,
+    "input": {
+      "year": 2400
+    },
+    "property": "leapYear"
+  },
+  {
+    "description": "year divisible by 200, not divisible by 400 in common year",
+    "expected": false,
+    "input": {
+      "year": 1800
+    },
+    "property": "leapYear"
+  }
+]
+=end code
diff --git a/raku/luhn/Luhn.rakumod b/raku/luhn/Luhn.rakumod
new file mode 100644
index 0000000..4b704b5
--- /dev/null
+++ b/raku/luhn/Luhn.rakumod
@@ -0,0 +1,4 @@
+unit module Luhn;
+
+sub is-luhn-valid(Str $input is copy --> Bool) is export {
+}
diff --git a/raku/luhn/README.md b/raku/luhn/README.md
new file mode 100644
index 0000000..fe6eb3a
--- /dev/null
+++ b/raku/luhn/README.md
@@ -0,0 +1,88 @@
+# Luhn
+
+Given a number determine whether or not it is valid per the Luhn formula.
+
+The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is
+a simple checksum formula used to validate a variety of identification
+numbers, such as credit card numbers and Canadian Social Insurance
+Numbers.
+
+The task is to check if a given string is valid.
+
+Validating a Number
+------
+
+Strings of length 1 or less are not valid. Spaces are allowed in the input,
+but they should be stripped before checking. All other non-digit characters
+are disallowed.
+
+## Example 1: valid credit card number
+
+```text
+4539 3195 0343 6467
+```
+
+The first step of the Luhn algorithm is to double every second digit,
+starting from the right. We will be doubling
+
+```text
+4_3_ 3_9_ 0_4_ 6_6_
+```
+
+If doubling the number results in a number greater than 9 then subtract 9
+from the product. The results of our doubling:
+
+```text
+8569 6195 0383 3437
+```
+
+Then sum all of the digits:
+
+```text
+8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80
+```
+
+If the sum is evenly divisible by 10, then the number is valid. This number is valid!
+
+## Example 2: invalid credit card number
+
+```text
+8273 1232 7352 0569
+```
+
+Double the second digits, starting from the right
+
+```text
+7253 2262 5312 0539
+```
+
+Sum the digits
+
+```text
+7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57
+```
+
+57 is not evenly divisible by 10, so this number is not valid.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+The Luhn Algorithm on Wikipedia [http://en.wikipedia.org/wiki/Luhn_algorithm](http://en.wikipedia.org/wiki/Luhn_algorithm)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/luhn/luhn.rakutest b/raku/luhn/luhn.rakutest
new file mode 100644
index 0000000..4bd0e63
--- /dev/null
+++ b/raku/luhn/luhn.rakutest
@@ -0,0 +1,165 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Luhn;
+plan 18;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  subtest %case<description>, {
+    plan 2;
+    isa-ok ( my $result := is-luhn-valid %case<input><value> ), Bool;
+    is-deeply $result, %case<expected>, 'Result matches expected';
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "single digit strings can not be valid",
+    "expected": false,
+    "input": {
+      "value": "1"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "a single zero is invalid",
+    "expected": false,
+    "input": {
+      "value": "0"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "a simple valid SIN that remains valid if reversed",
+    "expected": true,
+    "input": {
+      "value": "059"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "a simple valid SIN that becomes invalid if reversed",
+    "expected": true,
+    "input": {
+      "value": "59"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "a valid Canadian SIN",
+    "expected": true,
+    "input": {
+      "value": "055 444 285"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "invalid Canadian SIN",
+    "expected": false,
+    "input": {
+      "value": "055 444 286"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "invalid credit card",
+    "expected": false,
+    "input": {
+      "value": "8273 1232 7352 0569"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "invalid long number with an even remainder",
+    "expected": false,
+    "input": {
+      "value": "1 2345 6789 1234 5678 9012"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "valid number with an even number of digits",
+    "expected": true,
+    "input": {
+      "value": "095 245 88"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "valid number with an odd number of spaces",
+    "expected": true,
+    "input": {
+      "value": "234 567 891 234"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "valid strings with a non-digit added at the end become invalid",
+    "expected": false,
+    "input": {
+      "value": "059a"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "valid strings with punctuation included become invalid",
+    "expected": false,
+    "input": {
+      "value": "055-444-285"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "valid strings with symbols included become invalid",
+    "expected": false,
+    "input": {
+      "value": "055# 444$ 285"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "single zero with space is invalid",
+    "expected": false,
+    "input": {
+      "value": " 0"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "more than a single zero is valid",
+    "expected": true,
+    "input": {
+      "value": "0000 0"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "input digit 9 is correctly converted to output digit 9",
+    "expected": true,
+    "input": {
+      "value": "091"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "using ascii value for non-doubled non-digit isn't allowed",
+    "expected": false,
+    "input": {
+      "value": "055b 444 285"
+    },
+    "property": "valid"
+  },
+  {
+    "description": "using ascii value for doubled non-digit isn't allowed",
+    "expected": false,
+    "input": {
+      "value": ":9"
+    },
+    "property": "valid"
+  }
+]
+=end code
diff --git a/raku/nucleotide-count/NucleotideCount.rakumod b/raku/nucleotide-count/NucleotideCount.rakumod
new file mode 100644
index 0000000..03bae38
--- /dev/null
+++ b/raku/nucleotide-count/NucleotideCount.rakumod
@@ -0,0 +1,6 @@
+unit module NucleotideCount;
+
+subset DNA of Str where * !~~ /<-[ACGT]>/;
+sub nucleotide-count (DNA $strand) is export {
+    $strand.comb.Bag;
+}
diff --git a/raku/nucleotide-count/README.md b/raku/nucleotide-count/README.md
new file mode 100644
index 0000000..7ac2b56
--- /dev/null
+++ b/raku/nucleotide-count/README.md
@@ -0,0 +1,36 @@
+# Nucleotide Count
+
+Given a single stranded DNA string, compute how many times each nucleotide occurs in the string.
+
+The genetic language of every living thing on the planet is DNA.
+DNA is a large molecule that is built from an extremely long sequence of individual elements called nucleotides.
+4 types exist in DNA and these differ only slightly and can be represented as the following symbols: 'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' thymine.
+
+Here is an analogy:
+- twigs are to birds nests as
+- nucleotides are to DNA as
+- legos are to lego houses as
+- words are to sentences as...
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+The Calculating DNA Nucleotides_problem at Rosalind [http://rosalind.info/problems/dna/](http://rosalind.info/problems/dna/)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/nucleotide-count/nucleotide-count.rakutest b/raku/nucleotide-count/nucleotide-count.rakutest
new file mode 100644
index 0000000..51e405a
--- /dev/null
+++ b/raku/nucleotide-count/nucleotide-count.rakutest
@@ -0,0 +1,95 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use NucleotideCount;
+plan 5;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  given %case<expected> {
+    when .<error>.so {
+      throws-like
+        { nucleotide-count %case<input><strand> },
+        Exception,
+        message => /
+          $( %case<expected><error> )
+          || 'Constraint type check failed in binding to parameter'
+        /,
+        %case<description>;
+    }
+
+    default {
+      cmp-ok nucleotide-count(%case<input><strand>),
+        '~~', %case<expected>.Bag, %case<description>;
+    }
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "count all nucleotides in a strand: empty strand",
+    "expected": {
+      "A": 0,
+      "C": 0,
+      "G": 0,
+      "T": 0
+    },
+    "input": {
+      "strand": ""
+    },
+    "property": "nucleotideCounts"
+  },
+  {
+    "description": "count all nucleotides in a strand: can count one nucleotide in single-character input",
+    "expected": {
+      "A": 0,
+      "C": 0,
+      "G": 1,
+      "T": 0
+    },
+    "input": {
+      "strand": "G"
+    },
+    "property": "nucleotideCounts"
+  },
+  {
+    "description": "count all nucleotides in a strand: strand with repeated nucleotide",
+    "expected": {
+      "A": 0,
+      "C": 0,
+      "G": 7,
+      "T": 0
+    },
+    "input": {
+      "strand": "GGGGGGG"
+    },
+    "property": "nucleotideCounts"
+  },
+  {
+    "description": "count all nucleotides in a strand: strand with multiple nucleotides",
+    "expected": {
+      "A": 20,
+      "C": 12,
+      "G": 17,
+      "T": 21
+    },
+    "input": {
+      "strand": "AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC"
+    },
+    "property": "nucleotideCounts"
+  },
+  {
+    "description": "count all nucleotides in a strand: strand with invalid nucleotides",
+    "expected": {
+      "error": "Invalid nucleotide in strand"
+    },
+    "input": {
+      "strand": "AGXXACT"
+    },
+    "property": "nucleotideCounts"
+  }
+]
+=end code
diff --git a/raku/pangram/Pangram.rakumod b/raku/pangram/Pangram.rakumod
new file mode 100644
index 0000000..30618c8
--- /dev/null
+++ b/raku/pangram/Pangram.rakumod
@@ -0,0 +1,5 @@
+unit module Pangram;
+
+sub is-pangram (Str $string) is export {
+    $string.lc.comb ⊇ ('a'..'z');
+}
diff --git a/raku/pangram/README.md b/raku/pangram/README.md
new file mode 100644
index 0000000..09c35b7
--- /dev/null
+++ b/raku/pangram/README.md
@@ -0,0 +1,32 @@
+# Pangram
+
+Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma,
+"every letter") is a sentence using every letter of the alphabet at least once.
+The best known English pangram is:
+> The quick brown fox jumps over the lazy dog.
+
+The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case
+insensitive. Input will not contain non-ASCII symbols.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+Wikipedia [https://en.wikipedia.org/wiki/Pangram](https://en.wikipedia.org/wiki/Pangram)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/pangram/pangram.rakutest b/raku/pangram/pangram.rakutest
new file mode 100644
index 0000000..29725bd
--- /dev/null
+++ b/raku/pangram/pangram.rakutest
@@ -0,0 +1,101 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Pangram;
+plan 10;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  subtest %case<description>, {
+    plan 2;
+    isa-ok ( my $result := is-pangram %case<input><sentence> ), Bool;
+    is-deeply $result, %case<expected>, 'Result matches expected';
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "empty sentence",
+    "expected": false,
+    "input": {
+      "sentence": ""
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "perfect lower case",
+    "expected": true,
+    "input": {
+      "sentence": "abcdefghijklmnopqrstuvwxyz"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "only lower case",
+    "expected": true,
+    "input": {
+      "sentence": "the quick brown fox jumps over the lazy dog"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "missing the letter 'x'",
+    "expected": false,
+    "input": {
+      "sentence": "a quick movement of the enemy will jeopardize five gunboats"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "missing the letter 'h'",
+    "expected": false,
+    "input": {
+      "sentence": "five boxing wizards jump quickly at it"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "with underscores",
+    "expected": true,
+    "input": {
+      "sentence": "the_quick_brown_fox_jumps_over_the_lazy_dog"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "with numbers",
+    "expected": true,
+    "input": {
+      "sentence": "the 1 quick brown fox jumps over the 2 lazy dogs"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "missing letters replaced by numbers",
+    "expected": false,
+    "input": {
+      "sentence": "7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog"
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "mixed case and punctuation",
+    "expected": true,
+    "input": {
+      "sentence": "\"Five quacking Zephyrs jolt my wax bed.\""
+    },
+    "property": "isPangram"
+  },
+  {
+    "description": "case insensitive",
+    "expected": false,
+    "input": {
+      "sentence": "the quick brown fox jumps over with lazy FX"
+    },
+    "property": "isPangram"
+  }
+]
+=end code
diff --git a/raku/phone-number/Phone.rakumod b/raku/phone-number/Phone.rakumod
new file mode 100644
index 0000000..f2c828c
--- /dev/null
+++ b/raku/phone-number/Phone.rakumod
@@ -0,0 +1,16 @@
+unit module Phone;
+
+constant @errors = (
+   '11 digits must start with 1',
+   'more than 11 digits',
+   'incorrect number of digits',
+   'letters not permitted',
+   'punctuations not permitted',
+   'area code cannot start with zero',
+   'area code cannot start with one',
+   'exchange code cannot start with zero',
+   'exchange code cannot start with one',
+);
+
+sub clean-number ($number) is export {
+}
diff --git a/raku/phone-number/README.md b/raku/phone-number/README.md
new file mode 100644
index 0000000..ee01f5a
--- /dev/null
+++ b/raku/phone-number/README.md
@@ -0,0 +1,52 @@
+# Phone Number
+
+Clean up user-entered phone numbers so that they can be sent SMS messages.
+
+The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`.
+
+NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*.
+
+The format is usually represented as
+
+```text
+(NXX)-NXX-XXXX
+```
+
+where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9.
+
+Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code (1) if present.
+
+For example, the inputs
+- `+1 (613)-995-0253`
+- `613-995-0253`
+- `1 613 995 0253`
+- `613.995.0253`
+
+should all produce the output
+
+`6139950253`
+
+**Note:** As this exercise only deals with telephone numbers used in NANP-countries, only 1 is considered a valid country code.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+Event Manager by JumpstartLab [http://tutorials.jumpstartlab.com/projects/eventmanager.html](http://tutorials.jumpstartlab.com/projects/eventmanager.html)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/phone-number/phone-number.rakutest b/raku/phone-number/phone-number.rakutest
new file mode 100644
index 0000000..a8638ef
--- /dev/null
+++ b/raku/phone-number/phone-number.rakutest
@@ -0,0 +1,200 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Phone;
+plan 18;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  given %case<expected> {
+    when Str {
+      is clean-number(%case<input><phrase>),
+        |%case<expected description>;
+    }
+
+    when .<error>.so {
+      throws-like
+        { clean-number %case<input><phrase> },
+        Exception,
+        :message(%case<expected><error>),
+        %case<description>;
+    }
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "Cleanup user-entered phone numbers: cleans the number",
+    "expected": "2234567890",
+    "input": {
+      "phrase": "(223) 456-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: cleans numbers with dots",
+    "expected": "2234567890",
+    "input": {
+      "phrase": "223.456.7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: cleans numbers with multiple spaces",
+    "expected": "2234567890",
+    "input": {
+      "phrase": "223 456   7890   "
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid when 9 digits",
+    "expected": {
+      "error": "incorrect number of digits"
+    },
+    "input": {
+      "phrase": "123456789"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid when 11 digits does not start with a 1",
+    "expected": {
+      "error": "11 digits must start with 1"
+    },
+    "input": {
+      "phrase": "22234567890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: valid when 11 digits and starting with 1",
+    "expected": "2234567890",
+    "input": {
+      "phrase": "12234567890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: valid when 11 digits and starting with 1 even with punctuation",
+    "expected": "2234567890",
+    "input": {
+      "phrase": "+1 (223) 456-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid when more than 11 digits",
+    "expected": {
+      "error": "more than 11 digits"
+    },
+    "input": {
+      "phrase": "321234567890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid with letters",
+    "expected": {
+      "error": "letters not permitted"
+    },
+    "input": {
+      "phrase": "123-abc-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid with punctuations",
+    "expected": {
+      "error": "punctuations not permitted"
+    },
+    "input": {
+      "phrase": "123-@:!-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if area code starts with 0",
+    "expected": {
+      "error": "area code cannot start with zero"
+    },
+    "input": {
+      "phrase": "(023) 456-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if area code starts with 1",
+    "expected": {
+      "error": "area code cannot start with one"
+    },
+    "input": {
+      "phrase": "(123) 456-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if exchange code starts with 0",
+    "expected": {
+      "error": "exchange code cannot start with zero"
+    },
+    "input": {
+      "phrase": "(223) 056-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if exchange code starts with 1",
+    "expected": {
+      "error": "exchange code cannot start with one"
+    },
+    "input": {
+      "phrase": "(223) 156-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if area code starts with 0 on valid 11-digit number",
+    "expected": {
+      "error": "area code cannot start with zero"
+    },
+    "input": {
+      "phrase": "1 (023) 456-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if area code starts with 1 on valid 11-digit number",
+    "expected": {
+      "error": "area code cannot start with one"
+    },
+    "input": {
+      "phrase": "1 (123) 456-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if exchange code starts with 0 on valid 11-digit number",
+    "expected": {
+      "error": "exchange code cannot start with zero"
+    },
+    "input": {
+      "phrase": "1 (223) 056-7890"
+    },
+    "property": "clean"
+  },
+  {
+    "description": "Cleanup user-entered phone numbers: invalid if exchange code starts with 1 on valid 11-digit number",
+    "expected": {
+      "error": "exchange code cannot start with one"
+    },
+    "input": {
+      "phrase": "1 (223) 156-7890"
+    },
+    "property": "clean"
+  }
+]
+=end code
diff --git a/raku/raindrops/README.md b/raku/raindrops/README.md
new file mode 100644
index 0000000..29e6f61
--- /dev/null
+++ b/raku/raindrops/README.md
@@ -0,0 +1,39 @@
+# Raindrops
+
+Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. A factor is a number that evenly divides into another number, leaving no remainder. The simplest way to test if a one number is a factor of another is to use the [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation).
+
+The rules of `raindrops` are that if a given number:
+
+- has 3 as a factor, add 'Pling' to the result.
+- has 5 as a factor, add 'Plang' to the result.
+- has 7 as a factor, add 'Plong' to the result.
+- _does not_ have any of 3, 5, or 7 as a factor, the result should be the digits of the number.
+
+## Examples
+
+- 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong".
+- 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang".
+- 34 is not factored by 3, 5, or 7, so the result would be "34".
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division. [https://en.wikipedia.org/wiki/Fizz_buzz](https://en.wikipedia.org/wiki/Fizz_buzz)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/raindrops/Raindrops.rakumod b/raku/raindrops/Raindrops.rakumod
new file mode 100644
index 0000000..3b41065
--- /dev/null
+++ b/raku/raindrops/Raindrops.rakumod
@@ -0,0 +1,9 @@
+unit module Raindrops;
+
+sub raindrop(Int $num --> Str) is export {
+    my Str $res;
+    $res ~= "Pling" if $num %% 3;
+    $res ~= "Plang" if $num %% 5;
+    $res ~= "Plong" if $num %% 7;
+    return $res // $num.Str;
+}
diff --git a/raku/raindrops/raindrops.rakutest b/raku/raindrops/raindrops.rakutest
new file mode 100644
index 0000000..a61cfd8
--- /dev/null
+++ b/raku/raindrops/raindrops.rakutest
@@ -0,0 +1,165 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use Raindrops;
+plan 18;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  subtest %case<description>, {
+    plan 2;
+    isa-ok ( my $result := raindrop %case<input><number> ), Str;
+    is $result, %case<expected>, 'Result matches expected';
+  }
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "the sound for 1 is 1",
+    "expected": "1",
+    "input": {
+      "number": 1
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 3 is Pling",
+    "expected": "Pling",
+    "input": {
+      "number": 3
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 5 is Plang",
+    "expected": "Plang",
+    "input": {
+      "number": 5
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 7 is Plong",
+    "expected": "Plong",
+    "input": {
+      "number": 7
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 6 is Pling as it has a factor 3",
+    "expected": "Pling",
+    "input": {
+      "number": 6
+    },
+    "property": "convert"
+  },
+  {
+    "description": "2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base",
+    "expected": "8",
+    "input": {
+      "number": 8
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 9 is Pling as it has a factor 3",
+    "expected": "Pling",
+    "input": {
+      "number": 9
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 10 is Plang as it has a factor 5",
+    "expected": "Plang",
+    "input": {
+      "number": 10
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 14 is Plong as it has a factor of 7",
+    "expected": "Plong",
+    "input": {
+      "number": 14
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 15 is PlingPlang as it has factors 3 and 5",
+    "expected": "PlingPlang",
+    "input": {
+      "number": 15
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 21 is PlingPlong as it has factors 3 and 7",
+    "expected": "PlingPlong",
+    "input": {
+      "number": 21
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 25 is Plang as it has a factor 5",
+    "expected": "Plang",
+    "input": {
+      "number": 25
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 27 is Pling as it has a factor 3",
+    "expected": "Pling",
+    "input": {
+      "number": 27
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 35 is PlangPlong as it has factors 5 and 7",
+    "expected": "PlangPlong",
+    "input": {
+      "number": 35
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 49 is Plong as it has a factor 7",
+    "expected": "Plong",
+    "input": {
+      "number": 49
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 52 is 52",
+    "expected": "52",
+    "input": {
+      "number": 52
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7",
+    "expected": "PlingPlangPlong",
+    "input": {
+      "number": 105
+    },
+    "property": "convert"
+  },
+  {
+    "description": "the sound for 3125 is Plang as it has a factor 5",
+    "expected": "Plang",
+    "input": {
+      "number": 3125
+    },
+    "property": "convert"
+  }
+]
+=end code
diff --git a/raku/two-fer/README.md b/raku/two-fer/README.md
new file mode 100644
index 0000000..51dfd1d
--- /dev/null
+++ b/raku/two-fer/README.md
@@ -0,0 +1,49 @@
+# Two Fer
+
+`Two-fer` or `2-fer` is short for two for one. One for you and one for me.
+
+Given a name, return a string with the message:
+
+```text
+One for name, one for me.
+```
+
+Where "name" is the given name.
+
+However, if the name is missing, return the string:
+
+```text
+One for you, one for me.
+```
+
+Here are some examples:
+
+|Name    |String to return
+|:-------|:------------------
+|Alice   |One for Alice, one for me.
+|Bob     |One for Bob, one for me.
+|        |One for you, one for me.
+|Zaphod  |One for Zaphod, one for me.
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+[https://github.com/exercism/problem-specifications/issues/757](https://github.com/exercism/problem-specifications/issues/757)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/two-fer/TwoFer.rakumod b/raku/two-fer/TwoFer.rakumod
new file mode 100644
index 0000000..cf07952
--- /dev/null
+++ b/raku/two-fer/TwoFer.rakumod
@@ -0,0 +1,5 @@
+unit module TwoFer;
+
+sub two-fer ($name?) is export {
+    return "One for {$name // "you"}, one for me.";
+}
diff --git a/raku/two-fer/two-fer.rakutest b/raku/two-fer/two-fer.rakutest
new file mode 100644
index 0000000..f79c03e
--- /dev/null
+++ b/raku/two-fer/two-fer.rakutest
@@ -0,0 +1,49 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname; #`[Look for the module inside the same directory as this test file.]
+use TwoFer;
+plan 3; #`[This is how many tests we expect to run.]
+
+my @test-cases = from-json($=pod.pop.contents).List;
+# Go through the cases and check that &two-fer gives us the correct response.
+for @test-cases -> %case {
+  is do {
+    with %case<input><name> {
+      .&two-fer;
+    }
+    else {
+      two-fer;
+    }
+  }, |%case<expected description>;
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "no name given",
+    "expected": "One for you, one for me.",
+    "input": {
+      "name": null
+    },
+    "property": "twoFer"
+  },
+  {
+    "description": "a name given",
+    "expected": "One for Alice, one for me.",
+    "input": {
+      "name": "Alice"
+    },
+    "property": "twoFer"
+  },
+  {
+    "description": "another name given",
+    "expected": "One for Bob, one for me.",
+    "input": {
+      "name": "Bob"
+    },
+    "property": "twoFer"
+  }
+]
+=end code
diff --git a/raku/word-count/README.md b/raku/word-count/README.md
new file mode 100644
index 0000000..bf97113
--- /dev/null
+++ b/raku/word-count/README.md
@@ -0,0 +1,54 @@
+# Word Count
+
+Given a phrase, count the occurrences of each _word_ in that phrase.
+
+For the purposes of this exercise you can expect that a _word_ will always be one of:
+
+1. A _number_ composed of one or more ASCII digits (ie "0" or "1234") OR
+2. A _simple word_ composed of one or more ASCII letters (ie "a" or "they") OR
+3. A _contraction_ of two _simple words_ joined by a single apostrophe (ie "it's" or "they're")
+
+When counting words you can assume the following rules:
+
+1. The count is _case insensitive_ (ie "You", "you", and "YOU" are 3 uses of the same word)
+2. The count is _unordered_; the tests will ignore how words and counts are ordered
+3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are ignored
+4. The words can be separated by _any_ form of whitespace (ie "\t", "\n", " ")
+
+For example, for the phrase `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` the count would be:
+
+```text
+that's: 1
+the: 2
+password: 2
+123: 1
+cried: 1
+special: 1
+agent: 1
+so: 1
+i: 1
+fled: 1
+```
+
+## Resources
+
+Remember to check out the Raku [documentation](https://docs.raku.org/) and
+[resources](https://raku.org/resources/) pages for information, tips, and
+examples if you get stuck.
+
+## Running the tests
+
+There is a test suite and module included with the exercise.
+The test suite (a file with the extension `.rakutest`) will attempt to run routines
+from the module (a file with the extension `.rakumod`).
+Add/modify routines in the module so that the tests will pass! You can view the
+test data by executing the command `raku --doc *.rakutest` (\* being the name of the
+test suite), and run the test suite for the exercise by executing the command
+`prove6 .` in the exercise directory.
+
+## Source
+
+This is a classic toy problem, but we were reminded of it by seeing it in the Go Tour.
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/raku/word-count/WordCount.rakumod b/raku/word-count/WordCount.rakumod
new file mode 100644
index 0000000..62bc1a5
--- /dev/null
+++ b/raku/word-count/WordCount.rakumod
@@ -0,0 +1,5 @@
+unit module WordCount;
+
+sub count-words (Str $sentence) is export {
+    $sentence.lc.comb(/\w+\'\w+|\w+/).Bag;
+}
diff --git a/raku/word-count/word-count.rakutest b/raku/word-count/word-count.rakutest
new file mode 100644
index 0000000..45158aa
--- /dev/null
+++ b/raku/word-count/word-count.rakutest
@@ -0,0 +1,184 @@
+#!/usr/bin/env raku
+use Test;
+use JSON::Fast;
+use lib $?FILE.IO.dirname;
+use WordCount;
+plan 13;
+
+my @test-cases = from-json($=pod.pop.contents).List;
+for @test-cases -> %case {
+  cmp-ok count-words(%case<input><sentence>),
+    '~~', %case<expected>.Bag, %case<description>;
+}
+
+=head2 Test Cases
+=begin code
+[
+  {
+    "description": "count one word",
+    "expected": {
+      "word": 1
+    },
+    "input": {
+      "sentence": "word"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "count one of each word",
+    "expected": {
+      "each": 1,
+      "of": 1,
+      "one": 1
+    },
+    "input": {
+      "sentence": "one of each"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "multiple occurrences of a word",
+    "expected": {
+      "blue": 1,
+      "fish": 4,
+      "one": 1,
+      "red": 1,
+      "two": 1
+    },
+    "input": {
+      "sentence": "one fish two fish red fish blue fish"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "handles cramped lists",
+    "expected": {
+      "one": 1,
+      "three": 1,
+      "two": 1
+    },
+    "input": {
+      "sentence": "one,two,three"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "handles expanded lists",
+    "expected": {
+      "one": 1,
+      "three": 1,
+      "two": 1
+    },
+    "input": {
+      "sentence": "one,\ntwo,\nthree"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "ignore punctuation",
+    "expected": {
+      "as": 1,
+      "car": 1,
+      "carpet": 1,
+      "java": 1,
+      "javascript": 1
+    },
+    "input": {
+      "sentence": "car: carpet as java: javascript!!&@$%^&"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "include numbers",
+    "expected": {
+      "1": 1,
+      "2": 1,
+      "testing": 2
+    },
+    "input": {
+      "sentence": "testing, 1, 2 testing"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "normalize case",
+    "expected": {
+      "go": 3,
+      "stop": 2
+    },
+    "input": {
+      "sentence": "go Go GO Stop stop"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "with apostrophes",
+    "expected": {
+      "cry": 1,
+      "don't": 2,
+      "first": 1,
+      "laugh": 1,
+      "then": 1
+    },
+    "input": {
+      "sentence": "First: don't laugh. Then: don't cry."
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "with quotations",
+    "expected": {
+      "and": 1,
+      "between": 1,
+      "can't": 1,
+      "joe": 1,
+      "large": 2,
+      "tell": 1
+    },
+    "input": {
+      "sentence": "Joe can't tell between 'large' and large."
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "substrings from the beginning",
+    "expected": {
+      "a": 1,
+      "and": 1,
+      "app": 1,
+      "apple": 1,
+      "between": 1,
+      "can't": 1,
+      "joe": 1,
+      "tell": 1
+    },
+    "input": {
+      "sentence": "Joe can't tell between app, apple and a."
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "multiple spaces not detected as a word",
+    "expected": {
+      "multiple": 1,
+      "whitespaces": 1
+    },
+    "input": {
+      "sentence": " multiple   whitespaces"
+    },
+    "property": "countWords"
+  },
+  {
+    "description": "alternating word separators not detected as a word",
+    "expected": {
+      "one": 1,
+      "three": 1,
+      "two": 1
+    },
+    "input": {
+      "sentence": ",\n,one,\n ,two \n 'three'"
+    },
+    "property": "countWords"
+  }
+]
+=end code