Skip to content

Commit fea4285

Browse files
authored
Merge pull request #2 from sophilabs/adventures
Adventures
2 parents b4f9b08 + f99d168 commit fea4285

35 files changed

+795
-3
lines changed

learnregex/alternation/README.rst

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Alternation
2+
3+
If you want to match different regular expressions on the same string, you
4+
can use the alternation operator (the pipe symbol |) to separate different
5+
expressions and instruct the engine to try to match either what's to the left
6+
of it or, if it fails, what's to the right of it.
7+
8+
The alternation operator has the lowest precedence of all operators, meaning
9+
that the engine will try to match everything to its left as a whole and
10+
everything to its right (assuming the previous match failed) as a whole. If
11+
you want to limit the scope of the operator to use it inside a tiny part of a
12+
more complex expression you will need to learn how "groups" work. Luckily for
13+
you, that's the next adventure.
14+
15+
For this adventure, write a python function that receives a string and
16+
returns `True` if it's either 'red', 'green' or 'blue'; and `False` otherwise.
17+
18+
You can use this template to get started:
19+
20+
.. sourcecode:: python
21+
22+
import re
23+
24+
def test(string):
25+
# Your code goes here
26+
27+
HINT
28+
----
29+
You can use multiple alternation operators, they resolve from left to
30+
right. That means that the expression 'aa|bb|cc' will try to match 'aa'
31+
first, if it fails it will follow with 'bb|cc' where it will again split
32+
the expressions between the alternation operator and start with 'bb', and
33+
so on...
34+
35+
When you are done, you must run:
36+
37+
.. sourcecode:: bash
38+
39+
$ {script} verify program.py

learnregex/alternation/SOLUTION.rst

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
`program.py` content:
2+
3+
.. sourcecode:: python
4+
5+
import re
6+
7+
8+
def test(string):
9+
return re.match(r'red$|green$|blue$', string)
10+

learnregex/alternation/__init__.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import random
2+
3+
from story.adventures import AdventureVerificationError, BaseAdventure
4+
5+
from ..data import _
6+
from ..utils import load_solution_function
7+
8+
9+
class Adventure(BaseAdventure):
10+
11+
title = _('Alternation')
12+
choices = ['red', 'green', 'blue']
13+
14+
def test(self, file):
15+
function = load_solution_function(file)
16+
correct_argument = random.choice(self.choices)
17+
if not function(correct_argument):
18+
raise AdventureVerificationError(
19+
_("Your function didn't return True when executed with a "
20+
"correct argument '{}'.".format(correct_argument))
21+
)
22+
wrong_argument = (random.choice(self.choices)
23+
+ random.choice(self.choices))
24+
if function(wrong_argument):
25+
raise AdventureVerificationError(
26+
_("Your function returned True when executed with a wrong "
27+
"argument '{}'.".format(wrong_argument)))

learnregex/anchors/README.rst

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Anchors allow us to match specific positions inside a string instead of a
2+
character. They belong to a group called zero-length matches for this reason.
3+
4+
With anchors we can match the start and end of a string using ^ and $
5+
respectively.
6+
7+
For this adventure, write a python function that receives a string and
8+
returns `True` if it ends with a caret, and `False` otherwise.
9+
10+
You can use this template to get started:
11+
12+
.. sourcecode:: python
13+
14+
import re
15+
16+
def test(string):
17+
# Your code goes here
18+
19+
When you are done, you must run:
20+
21+
.. sourcecode:: bash
22+
23+
$ {script} verify program.py

learnregex/anchors/SOLUTION.rst

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
`program.py` content:
2+
3+
.. sourcecode:: python
4+
5+
import re
6+
7+
8+
def test(string):
9+
return re.match(r'.*\^$', string)

learnregex/anchors/__init__.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import string
2+
3+
from story.adventures import AdventureVerificationError, BaseAdventure
4+
5+
from ..data import _
6+
from ..utils import get_random_string, load_solution_function
7+
8+
9+
class Adventure(BaseAdventure):
10+
11+
title = _('Anchors')
12+
dictionary = string.ascii_lowercase + string.digits
13+
14+
def test(self, file):
15+
function = load_solution_function(file)
16+
correct_argument = get_random_string(self.dictionary, 4, 6) + '^'
17+
if not function(correct_argument):
18+
raise AdventureVerificationError(
19+
_("Your function didn't return True when executed with a "
20+
"correct argument '{}'.".format(correct_argument))
21+
)
22+
wrong_argument = get_random_string(self.dictionary, 4, 6)
23+
if function(wrong_argument):
24+
raise AdventureVerificationError(
25+
_("Your function returned True when executed with a wrong "
26+
"argument '{}'.".format(wrong_argument)))

learnregex/capturing/README.rst

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Groups provide an additional feature: they capture the string they end up
2+
matching and save it in a variable that we can use later, either in the same
3+
regular expression or after it finishes. In python we can use a captured
4+
group with a backslash and an index. For example: \1 will reference the first
5+
group of our regular expression, \2 will reference the second one and so on
6+
until \9, where we run out of numbers (they can only use one digit). If you
7+
need to capture more than 9 groups check out named groups.
8+
9+
Say we want to test if a string ends with the same character it started. We
10+
could write something like this: '^(.).*\1$|^.$'. We capture the first
11+
character after the start of the string, match zero o more characters in
12+
between and reference the captured character before the string ends. We also
13+
alternate with another expression in case our string has only 1 character.
14+
15+
For this adventure, write a python function that receives a string with only
16+
one pipe ('|') somewhere in between and returns `True` if everything to the
17+
left of the pipe equals what's to its right, and `False` otherwise.
18+
19+
You can use this template to get started:
20+
21+
.. sourcecode:: python
22+
23+
import re
24+
25+
def test(string):
26+
# Your code goes here

learnregex/capturing/SOLUTION.rst

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
`program.py` content:
2+
3+
.. sourcecode:: python
4+
5+
import re
6+
7+
8+
def test(string):
9+
return re.match(r'(.*)\|\1$', string)
10+

learnregex/capturing/__init__.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import string
2+
3+
from story.adventures import AdventureVerificationError, BaseAdventure
4+
5+
from ..data import _
6+
from ..utils import get_random_string, load_solution_function
7+
8+
9+
class Adventure(BaseAdventure):
10+
11+
title = _('Capturing')
12+
dictionary = string.ascii_lowercase + string.digits
13+
14+
def test(self, file):
15+
function = load_solution_function(file)
16+
repeat = get_random_string(self.dictionary, 4, 6)
17+
correct_argument = '{0}|{0}'.format(repeat)
18+
if not function(correct_argument):
19+
raise AdventureVerificationError(
20+
_("Your function didn't return True when executed with a "
21+
"correct argument '{}'.".format(correct_argument))
22+
)
23+
wrong_argument = '{}|{}'.format(
24+
get_random_string(self.dictionary, 5, 5),
25+
get_random_string(self.dictionary, 5, 5)
26+
)
27+
if function(wrong_argument):
28+
raise AdventureVerificationError(
29+
_("Your function returned True when executed with a wrong "
30+
"argument '{}'.".format(wrong_argument)))
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Now that we now what special characters are and how to escape them, let's try
2+
their actual special meaning. We are going to start with character classes.
3+
4+
A character class matches one out of a set of characters that we define. You
5+
define a character class by writing all the characters of your set between
6+
square brackets. For example, this matches either an "a" or a "b": [ba] (the
7+
order doesn't matter).
8+
9+
For this adventure, write a python function that receives a string and
10+
returns `True` if the first character is a digit, and `False` otherwise.
11+
12+
You can use this template to get started:
13+
14+
.. sourcecode:: python
15+
16+
import re
17+
18+
def test(string):
19+
# Your code goes here
20+
21+
When you are done, you must run:
22+
23+
.. sourcecode:: bash
24+
25+
$ {script} verify program.py
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
`program.py` content:
2+
3+
.. sourcecode:: python
4+
5+
import re
6+
7+
8+
def test(string):
9+
return re.match(r'[0123456789]', string)
10+
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import random
2+
import string
3+
4+
from story.adventures import AdventureVerificationError, BaseAdventure
5+
6+
from ..data import _
7+
from ..utils import get_random_string, load_solution_function
8+
9+
10+
class Adventure(BaseAdventure):
11+
12+
title = _('Character classes')
13+
dictionary = string.ascii_lowercase
14+
15+
def test(self, file):
16+
function = load_solution_function(file)
17+
correct_argument = '{}{}'.format(
18+
random.randint(0, 9),
19+
get_random_string(self.dictionary, 1, 5)
20+
)
21+
if not function(correct_argument):
22+
raise AdventureVerificationError(
23+
_("Your function didn't return True when executed with a "
24+
"correct argument '{}'.".format(correct_argument))
25+
)
26+
wrong_argument = get_random_string(self.dictionary, 1, 5)
27+
if function(wrong_argument):
28+
raise AdventureVerificationError(
29+
_("Your function returned True when executed with a wrong "
30+
"argument '{}'.".format(wrong_argument)))

learnregex/greediness/README.rst

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
When using quantifiers sometimes we'll run into an issue where they match
2+
more than we want. Suppose we want to match the first part (including the
3+
dot) of a domain. We write the expression '.*\.' thinking "this will return
4+
all the characters before the first dot along with it" and test it on
5+
"pyschool.github.io" expecting to get "pyschool." back, but it returns
6+
"pyschool.github.".
7+
8+
What happened? Quantifiers are greedy by default, meaning they will match
9+
all the characters they can. In this case we have two dots in our string, so
10+
the quantifier gets to match all the characters up to the last dot without
11+
problem.
12+
13+
We can make our quantifiers "lazy" by adding a question mark at their end
14+
(even the ? quantifier, which becomes ?? in its lazy form), meaning they will
15+
match the minimum amount of characters they can. If we add a question mark to
16+
our quantifier it becomes lazy and will now match all the characters up to
17+
the first dot and stop there, since the whole expression matches.
18+
19+
For this adventure, write a python function that receives a string of comma
20+
separated values and returns all the characters from the start up to the
21+
first comma, including it.
22+
23+
You can use this template to get started:
24+
25+
.. sourcecode:: python
26+
27+
import re
28+
29+
def test(string):
30+
# Your code goes here
31+
32+
When you are done, you must run:
33+
34+
.. sourcecode:: bash
35+
36+
$ {script} verify program.py

learnregex/greediness/SOLUTION.rst

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
`program.py` content:
2+
3+
.. sourcecode:: python
4+
5+
import re
6+
7+
8+
def test(string):
9+
return re.match(r'.*?,', string).group(0)
10+

learnregex/greediness/__init__.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import string
2+
3+
from story.adventures import AdventureVerificationError, BaseAdventure
4+
5+
from ..data import _
6+
from ..utils import get_random_string, load_solution_function
7+
8+
9+
class Adventure(BaseAdventure):
10+
11+
title = _('Greediness')
12+
dictionary = string.ascii_lowercase + string.digits
13+
14+
def test(self, file):
15+
function = load_solution_function(file)
16+
prefix = get_random_string(self.dictionary, 1, 5) + ','
17+
correct_argument = '{}{},{}'.format(
18+
prefix,
19+
get_random_string(self.dictionary, 1, 5),
20+
get_random_string(self.dictionary, 1, 5),
21+
)
22+
result = function(correct_argument)
23+
if result != prefix:
24+
raise AdventureVerificationError(
25+
_("Your function didn't return the expected string '{}' when "
26+
"executed with '{}'. "
27+
"It returned '{}'.".format(prefix, correct_argument, result))
28+
)

learnregex/groups/README.rst

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Groups have plenty of uses. They basically enclose a section of our
2+
expression so we can treat it as a token. This allows us to apply quantifiers
3+
to a whole expression instead of a single character or character class like
4+
we have been doing. It also allows us to use the alternation operator with a
5+
limited scope.
6+
7+
Groups are defined with simple parenthesis. We can put everything inside them,
8+
even other groups.
9+
10+
For this adventure, write a python function that receives a string and
11+
returns `True` if starts with one or more repetitions of the word 'hello' and
12+
ends with either 'python' or 'pyschool', and `False` otherwise. You don't
13+
need to check for spaces, the words just need to follow one immediately after
14+
another.
15+
16+
You can use this template to get started:
17+
18+
.. sourcecode:: python
19+
20+
import re
21+
22+
def test(string):
23+
# Your code goes here

learnregex/groups/SOLUTION.rst

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
`program.py` content:
2+
3+
.. sourcecode:: python
4+
5+
import re
6+
7+
8+
def test(string):
9+
return re.match(r'(hello)+(python|pyschool)', string)
10+

0 commit comments

Comments
 (0)