Today: more string algorithms, str in, if/elif, quick-return, str find, slices

Note: running tests, green run button near bottom

See Python guide: string, if

Problems today:

> str2 functions

## String `in` Test

• Built-ins xxx
• String has many built-in functions, we'll look at the most important
• `in` - test if a substring appears in a string
• As usual, chars must match exactly - upper/lower are considered different
• True/False test - use .find() is need to know where char is
```>>> 'c' in 'abcd'
True
>>> 'bc' in 'abcd'
True
>>> 'bx' in 'abcd'
False
>>> 'A' in 'abcd'
False
```

## 1. Catty function

`'xCtxxxAax' → 'CtAa'`

Return a string made of the chars from the original string, whenever the chars are 'c' 'a' or 't', (either lower or upper case). So the string 'xCaxxxTx' returns 'CaT'.

• Compute lowercase version of each char
• Create var "low" to hold intermediate result (strategy)
• Can write with "or"
• Can write with "in" - cute

Solution

```def catty(s):
result = ''
for i in range(len(s)):
# Create "low" var of intermediate result
low = s[i].lower()
if low == 'c' or low == 'a' or low == 't':
result += s[i]  # Use original, not low
return result
# Cute non-obvious alternative: use "in"
# to do the "or" logic for us, like this:
# if low in 'cat':
```

## if/elif Structure

• Most common: regular if
• Second: if/else
Choose between 2 actions
• if/elif structure
• Evaluate test1, test2, test3
• As soon as test is true, run that action. We are done
• Optional "else:" at end runs if no test is true
```if test1:
action-1
elif test2:
action-2
else:
action-3
```

• If/elif logic testing each char
• alpha, digit, otherwise

Solution

```def str_adx(s):
result = ''
for i in range(len(s)):
if s[i].isalpha():
result += 'a'
elif s[i].isdigit():
result += 'd'
else:
result += 'x'
return result
```

## Quick Return Strategy

• Discover answer: return with it right away
• Loop over everything looking for X
• Never find X
• Therefore there is no X
• return at bottom for no-x case

## 3. first_alpha()

Given a string s, return the first alphabetic char in s, or None if there is no alphabetic char. Demonstrates quick-return strategy.

Solution

```def first_alpha(s):
for i in range(len(s)):
if s[i].isalpha():
return s[i]
# 1. Exit immediately if found
# 2. If we get here,
# there was no alpha char.
return None
```

## 4. has_digit()

Use quick-return strategy, returning True or False.

Given a string s, return True if there is a digit in the string somewhere, False otherwise.

```    for i in range(len(s)):
if s[i].isdigit():
# 1. Exit immediately if found
return True
# 2. If we get here,
# there was no digit.
return False
```

## String find()

• `s.find(target_str)`- search s for target_str
• Returns int index where found first
• str noun.verb function call style (aka "object oriented")
• Take optional 2nd "start_index" parameter, starts search from there
• `s.find(target_str, start_index)`
```>>> s = 'there'
>>> s.find('h')
1
>>> s.find('e')
2
>>> s.find('ere')   # len > 1 ok
2
>>> s.find('x')
-1
>>> s.find('E')
-1
>>> s.find('e', 3)  # start at 3
4
```

## Python String Slices • This is a great feature
• Dense! i.e. slow down when writing a slice
• "substring" - contiguous sub-part of a string
• Access substring with 2 numbers
• "slice" uses colon to indicate a range of indexes
• `s[1:3]`
• Start at first number
• Up to but not including second number
• If start index is omitted, uses start of string
• If end index is omitted, uses end of string
• If number is too big .. uses end also
• Note perfect split: s[:4] and s[4:]
Number used as both start and end
Splits the str into 2 pieces
• `s[3:3]` = empty string
"Not including" number dominates
• Try it in the interpreter
• Style: typically written with no spaces around ":"
```>>> s = 'Python'
>>> s[1:3]
'yt'
>>> s[1:5]
'ytho'
>>> s[3:3]    # "not including" dominates
''
>>>
>>> s[:3]     # omit = from/to end
'Pyt'
>>> s[4:]
'on'
>>> s[4:999]  # too big = to end
'on'
>>> s[:4]     # "perfect split" on 4
'Pyth'
>>> s[4:]
'on'
>>> s[:]      # the whole thing
'Python'
```

## Brackets Problem

• Problems like: extracting email address from a text message
• Given `cat[dog]bird' → `dog`
• Given: either 2 brackets, or zero brackets
• Strategy:
• Use .find()
• `left = s.find('[')`
• Store value in variable `left` for later lines
Partial results in well-named variable
An important technique
• Look for right bracket
• Use slice to pull out and return answer

## Brackets Observations

• Make a diagram - work out the index numbers
• Code should work in general
• BUT can use specific string to work out numbers
• e.g. 'cat[dog]bird'
• Empty string input - works?
Verify that our slice works here too

## Optional: Negative Slice

• You do not need to use these!
• Handy shortcut, a little advanced
• Negative numbers to refer to chars at end of string
• -1 is the last char
• -2 is the next to last char
• Works in slices etc.
```>>> s = 'Python'
>>> s[len(s)-1]
'n'
>>> s[-1]
'n'
>>> s[-2]
'o'
>>> s[1:-3]
'yt'
>>> s[-3:]
'hon'
```