Today: string char class, more string loops, if/else, Doctests, grid, peeps example
def double_char(s):
result = ''
for i in range(len(s)):
result += s[i] + s[i]
return result
s.isalpha() - True for alphabetic word char, i.e. 'a-z' and 'A-Z'. Python uses "unicode" to support many alphabets, e.g. 'Ω' is another alphabetic char.
s.isdigit() - True if all chars in s are digits '0' '1' .. '9'
s.isalnum() - alphanumeric, just combines isalpha() and isdigit()
s.isspace() - True for whitespace char, e.g. space, tab, newline
>>> 'a'.isalpha() True >>> 'abc'.isalpha() # works for multiple chars too True >>> 'Z'.isalpha() True >>> '$'.isalpha() False >>> '@'.isalpha() False >>> '9'.isdigit() True >>> ' '.isspace() True >>> 'Ω'.isalpha() # Unicode True >>> 'Ω'.isdigit() False
Solution code
def alpha_only(s):
result = ''
# Loop over all index numbers
for i in range(len(s)):
# Access each s[i]
if s[i].isalpha():
result += s[i]
return result
Just as a reminder, here is the regular if-statement we've used many times. If the test is True, the lines are run. Otherwise if the test is False, the lines are skipped.
if test-expression: Lines-A
See guide for more if/else details: Python-if
if test-expression: Lines-A else: Lines-B
> str_dx
Solution code
def str_dx(s):
result = ''
for i in range(len(s)):
if s[i].isdigit():
result += 'd'
else:
result += 'x'
return result
Suppose you want to do something if a test is False. Sometimes people sort of back into using else to do that, like the following, but this is not the best way:
if some_test:
pass # do nothing here
else:
do_something
The correct way to do that is with not:
if not some_test:
do_something
You know at some level that the interior of the computer is very logical. This problem shows that logical quality at work.
Consider this function:
has_alpha(s): Given string s. Return True if there is an alphabetic char within s. If there is no alphabetic char within s, return False.
Is this algorithm like the book Are You My Mother?
Think about looking through the chars of the string. When do you know the answer?
Logic strategy: look at each char. (1) If we see an alpha char, return True immediately. We do not need to look at any more chars. (2) If we look at every char, and never see an alpha char, must conclude that there are no alpha chars and the result should be False. The (1) code goes in the loop. The (2) code goes after the loop - a sort of "by exhaustion" strategy.
def has_alpha(s):
for i in range(len(s)):
if s[i].isalpha():
# 1. Return True immediately if find
# alpha char - we're done.
return True
# 2. If we get to here, there was no
# alpha char, so return False.
return False
Big Picture - program made of many functions. Want to build out the functions efficiently, concentrating on one function at a time. The ability to test each function separately is great time-saver.
Given string s, return a string of its digit chars.
'Hi4x3' -> '43'
Doctests are the Python technology for easily testing a function.
For more details, see the Doctest chapter in the guide
def digits_only(s):
"""
Given a string s.
Return a string made of all
the chars in s which are digits,
so 'Hi4!x3' returns '43'.
Use a for/i/range loop.
(this code is complete)
>>> digits_only('Hi4!x3')
'43'
>>> digits_only('123')
'123'
>>> digits_only('')
''
"""
result = ''
for i in range(len(s)):
if s[i].isdigit():
result += s[i]
return result
Here are the key lines that make one Doctest:
>>> digits_only('Hi4!x3')
'43'
We'll use Doctests to drive the examples in section and on homework-3.
Today's grid example peeps.zip
grid = Grid(3, 2) grid.width # returns 3 grid.set(2, 0, 'a') grid.set(2, 1, 'b') grid.get(2, 0) -> 'a' grid.in_bounds(2, 0) -> True
Suppose we have a 2-d grid of peeps candy bunnies. A square in the grid is either 'p' if it contains a peep, or is None if empty.
We'll say that a peep is "happy" if it has another peep immediately to its left or right.
Look at the grid squares again. For each x,y .. is that a happy peep x,y?
x, y happy? (top row) 0, 0 -> False (no peep there) 1, 0 -> True 2, 0 -> True (2nd row, nobody happy) 0, 1 -> False 1, 1 -> False 2, 1 -> False
Here is the syntax for the above grid. The first [ .. ] is the first row, the second [ .. ] is the second row. This is fine for writing the data of a small grid, which is good enough for writing a test.
grid = Grid.build([[None, 'p', 'p'], ['p', None, 'p']])
def is_happy(grid, x, y):
"""
Given a grid of peeps and in bounds x,y.
Return True if there is a peep at that x,y
and it is happy.
A peep is happy if there is another peep
immediately to its left or right.
>>> grid = Grid.build([[None, 'p', 'p'], ['p', None, 'p']])
>>> is_happy(grid, 0, 0)
False
>>> is_happy(grid, 1, 0)
True
>>> is_happy(grid, 2, 0)
True
>>> is_happy(grid, 0, 1)
False
>>> is_happy(grid, 2, 1)
False
"""
pass
This code is fine. Using the "pick-off strategy, looking for cases to return True. Then return False as the bottom if none of the cases found another peep.
def is_happy(grid, x, y):
"""
Given a grid of peeps and in bounds x,y.
Return True if there is a peep at that x,y
and it is happy.
A peep is happy if there is another peep
immediately to its left or right.
>>> grid = Grid.build([[None, 'p', 'p'], ['p', None, 'p']])
>>> is_happy(grid, 0, 0)
False
>>> is_happy(grid, 1, 0)
True
>>> is_happy(grid, 2, 0)
True
>>> is_happy(grid, 0, 1)
False
>>> is_happy(grid, 2, 1)
False
"""
# 1. Check if there's a peep at x,y
# If not we can return False immediately.
if grid.get(x, y) != 'p':
return False
# 2. Happy because of peep to left?
# Must check that x-1 is in bounds before calling get()
if grid.in_bounds(x - 1, y):
if grid.get(x - 1, y) == 'p':
return True
# 3. Similarly, is there a peep to the right?
if grid.in_bounds(x + 1, y):
if grid.get(x + 1, y) == 'p':
return True
# 4. If we get to here, not a happy peep,
# so return False
return False
andThe in_bounds() checks can be done with and instead nesting 2 ifs. This works because the "and" works through its tests left-to-right, and stops as soon as it gets a False. This code is a little shorter, but both approaches are fine.
# 2. Happy because of peep to left?
# here using "and" instead of 2 ifs
if grid.in_bounds(x - 1, y) and grid.get(x - 1, y) == 'p':
return True
Another function in peeps.py. Works on a whole column, instead of just one x,y. The return True/False strategy is similar to the one seen in the has_alpha() example above. Doctests are provided, write the code to make it work.
def has_happy(grid, x):
"""
Given grid of peeps and an in-bounds x.
Return True if there is a happy peep in
that column somewhere, or False if there
is no happy peep.
>>> grid = Grid.build([[None, 'p', 'p'], ['p', None, 'p']]) # same grid as before
>>> has_happy(grid, 0)
False
>>> has_happy(grid, 1)
True
"""
# your code here
pass
def has_happy(grid, x):
"""
Given grid of peeps and an in-bounds x.
Return True if there is a happy peep in
that column somewhere, or False if there
is no happy peep.
>>> grid = Grid.build([[None, 'p', 'p'], ['p', None, 'p']]) # same grid as before
>>> has_happy(grid, 0)
False
>>> has_happy(grid, 1)
True
"""
# x is specified in parameter, loop over all y
for y in range(grid.height):
if is_happy(grid, x, y):
return True
# if we get here .. no happy peep found
return False
We're just starting down this path with Doctests. Doctests enable writing little tests for each black-box function as you go, which turns out to be big productivity booster. We will play with this in section and on homework-3.