Exit slides Esc

Unit 14 — What Does This Program Do? (Strings)

Slide 1 of 56

  1. The problem: The Password Machine

    The coding club gives every locker a password. The password machine takes a secret word, cuts it into pieces, and glues the pieces back together in a new order. This is its ACSL program:

    INPUT S
    A = S[:2]
    B = S[3:]
    C = S[1:3]
    P = B + C + A
    OUTPUT P, len(P)

    What is output when the input is S = "ELEPHANT"?

  2. This is the Junior What Does This Program Do? question for Contest 4: a short program that works on a string, a row of characters, traced by hand with no computer. The IFs and loops are the ones you already know from Units 3, 6 and 10. What is new are the pieces. S[3:] and S[1:3] look exactly like Python, but in ACSL they mean something different. Read them the Python way and every line after them goes wrong.

  3. This unit teaches it in three steps: how ACSL numbers the characters of a string and cuts pieces out of it, and how to write each piece in Python (Lesson 1), how programs walk along a string to count, build, reverse and test for palindromes (Lesson 2), and how programs move a piece along a string, compare letters, and shrink a string in a loop (Lesson 3). Write down your first guess now. You will check it at the end of Lesson 1.

    • Read ACSL string positions, which start at 0, with len(S) and S[j].

    • Apply ACSL's three substring forms, translate them to Python, and predict each piece's length.

    • Trace loops that count, build, reverse and compare strings, including palindrome tests.

    • Compare letters by alphabetical order, move a piece along a string, and shrink a string with WHILE.

  4. Lesson 1 — Strings and pieces in ACSL and in Python

    Division: Junior and above.

    A string is a row of characters: letters, digits, spaces and signs. Each character has a position, and in ACSL, as in Python, the first position is 0. len(S) is the number of characters, and S[j] is the character at position j.

    For S = "ELEPHANT":

    position 0 1 2 3 4 5 6 7
    character E L E P H A N T

    On paper, write the positions above the letters like this before you trace anything.

  5. S = "ELEPHANT"
    print(len(S))
    print(S[0], S[3], S[len(S) - 1])

    Output

    8
    E P T
    

    len(S) is 8, but the last position is 7, one less, because the positions start at 0. S[len(S) - 1] is always the last character, whatever the length. S[8] would be past the end: there is no position 8.

  6. Three ways to cut a piece

    A piece of a string (a substring) is written with a colon inside the square brackets. ACSL has exactly three forms, and only the first one means the same as in Python.

    ACSL means for "ELEPHANT" Python
    S[:n] the first n characters S[:2] is "EL" S[:n]
    S[n:] the last n characters S[3:] is "ANT" S[len(S) - n:]
    S[a:b] positions a through b, both included S[1:3] is "LEP" S[a:b + 1]

    Here is the table as Python.

  7. S = "ELEPHANT"
    print(S[:2])
    print(S[len(S) - 3:])
    print(S[1:3 + 1])

    Output

    EL
    ANT
    LEP
    

    The three pieces are EL, ANT and LEP. In S[n:] the number is a count from the right-hand end, not a position. In S[a:b] both ends are positions, and the piece includes both of them.

  8. The trap: reading ACSL with Python's eyes

    You already know Python's slices, and that is exactly the danger. Python's S[3:] starts at position 3 and runs to the end. Python's S[1:3] starts at position 1 and stops before position 3.

  9. S = "ELEPHANT"
    print(S[3:])
    print(S[1:3])

    Output

    PHANT
    LE
    

    Python gives PHANT and LE, while ACSL's S[3:] is ANT and ACSL's S[1:3] is LEP. When a question shows ACSL pseudocode, use ACSL's rules; when it shows Python, use Python's rules. Before you trace, look at which language the program is in.

  10. How long is each piece?

    Counting the characters of a piece is a quick check on your work.

    ACSL number of characters
    S[:n] n
    S[n:] n
    S[a:b] b - a + 1

    So S[j:j] is one character, the same as S[j], and S[j:j + 1] is two characters. Two pieces come up again and again:

    • S[:len(S) - 1] is every character except the last one.

    • S[len(S) - 1:] is the last len(S) - 1 characters: every character except the first one.

  11. T = "PENCIL"
    print(T[2:2 + 1], len(T[1:4 + 1]))
    print(T[:len(T) - 1])
    print(T[len(T) - (len(T) - 1):])

    Output

    N 4
    PENCI
    ENCIL
    

    ACSL's T[2:2] is N, a single character, and ACSL's T[1:4] has 4 - 1 + 1, which is 4, characters. ACSL's T[len(T) - 1:] asks for the last 5 characters of PENCIL, which is ENCIL: the first letter has gone. In Python that is T[1:], which the translation T[len(T) - (len(T) - 1):] works out to.

  12. Joining and comparing

    + joins two strings end to end. It adds no space: "ANT" + "EATER" is "ANTEATER". == is true only when two strings are exactly the same: the same length, and the same character at every position. A space counts as a character, and so does its absence.

  13. print("ANT" + "EATER")
    print("CAT" == "CA" + "T")
    print("CAT" == "CAT ")
    print(len("ICE" + " " + "CREAM"))

    Output

    ANTEATER
    True
    False
    9
    
  14. "CAT ", with a space on the end, is not equal to "CAT", because it is one character longer. "ICE" + " " + "CREAM" has 3 + 1 + 5, which is 9, characters, because the space you joined in counts too.

    When an ACSL program outputs several values, as in OUTPUT P, len(P), this book writes them on one line separated by one space, just as Python's print(P, len(P)) does. The strings are written without quotation marks.

  15. Checking the opening problem

    Here is the password machine, translated line by line. S[3:] becomes S[len(S) - 3:] and S[1:3] becomes S[1:4].

  16. S = "ELEPHANT"
    A = S[:2]
    B = S[len(S) - 3:]
    C = S[1:4]
    P = B + C + A
    print(P, len(P))

    Output

    ANTLEPEL 8
    

    A is EL, B is ANT and C is LEP, so the password is ANTLEPEL, with 8 characters. Reading it with Python's eyes gives PHANT, LE and a wrong password, PHANTLEEL, with 9. Did it match your guess? Running a translation is how you check an answer while you practise; in the contest you work it out by hand, piece by piece.

  17. Put it together: this program reads a word S and a number n, and makes a badge: the first n letters joined to the last n letters. It also outputs the middle: the letters left over between them, positions n through len(S) - 1 - n.

    INPUT S
    INPUT n
    badge = S[:n] + S[n:]
    middle = S[n:len(S) - 1 - n]
    OUTPUT badge, middle
  18. The first input line holds the word, in capital letters, and the second line holds n. The word has at least 2 * n + 1 letters, so the middle is never empty. It is saved as assets/l1.py.

  19. Try it
    S = input()
    n = int(input())
    badge = S[:n] + S[len(S) - n:]
    middle = S[n:len(S) - n]
    print(badge, middle)
  20. Run it from this unit's folder, feeding it an input file:

    python assets/l1.py < assets/l1/1.in
  21. All three forms appear, and each one is translated differently. ACSL's S[n:len(S) - 1 - n] ends at position len(S) - 1 - n, so the Python slice must stop one later, at len(S) - n. With the word CAT and n = 1 (assets/l1/2.in) the middle is the single letter A.

  22. Lesson 2 — Walking along a string

    Division: Junior and above.

    Many programs look at a string one character at a time. The positions run from 0 to len(S) - 1, so the loop is

    FOR j = 0 TO len(S) - 1
       ...S[j]...
    NEXT j

    The - 1 matters: ACSL's FOR includes its end value, and position len(S) does not exist. In Python it becomes for j in range(len(S)):, because range stops before its end.

  23. Counting one character

    To count how often one character appears, compare each S[j] with it and add 1 when they match.

    S = "EXCELLENCE"
    c = 0
    FOR j = 0 TO len(S) - 1
       IF S[j] == "E" THEN
          c = c + 1
       END IF
    NEXT j
    OUTPUT c

    Python can also walk along a string without positions, with for ch in S:. Both loops are shown here.

  24. S = "EXCELLENCE"
    c = 0
    for j in range(len(S)):
        if S[j] == "E":
            c = c + 1
    print(c)
    d = 0
    for ch in S:
        if ch == "E":
            d = d + 1
    print(d)

    Output

    4
    4
    

    Both loops count 4. for ch in S is shorter, but it does not know where each character is. When a program uses the position too, as in S[j + 1] or S[len(S) - 1 - j], use the loop over positions.

  25. Counting a group of characters: vowels

    To count several different characters, join the tests with || (Python's or).

    IF S[j] == "A" || S[j] == "E" || S[j] == "I" || S[j] == "O" || S[j] == "U" THEN
  26. S = "EDUCATION"
    v = 0
    for j in range(len(S)):
        ch = S[j]
        if ch == "A" or ch == "E" or ch == "I" or ch == "O" or ch == "U":
            v = v + 1
    print(v, len(S) - v)

    Output

    5 4
    

    EDUCATION has 5 vowels and 4 other letters. Each test must name ch again: ch == "A" or "E" does not test for E. In a trace, tick each vowel as you pass it, then count the ticks.

  27. Building a new string

    A program can build a new string, starting from the empty string "" and joining one character on each pass.

    S = "BASKETBALL"
    T = ""
    FOR j = 0 TO len(S) - 1
       IF j % 2 == 0 THEN
          T = T + S[j]
       END IF
    NEXT j
    OUTPUT T
  28. S = "BASKETBALL"
    T = ""
    for j in range(len(S)):
        if j % 2 == 0:
            T = T + S[j]
    print(T)

    Output

    BSEBL
    

    j % 2 == 0 keeps the positions 0, 2, 4, 6 and 8, which hold B, S, E, B and L: the answer is BSEBL. It is easy to count letters from 1 by mistake and keep A, K, T, A, L instead. Write the positions above the word first.

  29. Reversing: which side do you join on?

    T = T + S[j] joins each character on the end, so it copies the string in order. R = S[j] + R joins each character on the front, so the first character ends up last: the string comes out reversed.

    A loop that runs backwards and joins on the end reverses too:

    FOR j = len(S) - 1 TO 0 STEP -1
       R = R + S[j]
    NEXT j
  30. S = "DRAWER"
    T = ""
    R = ""
    for j in range(len(S)):
        T = T + S[j]
        R = S[j] + R
    print(T, R)
    B = ""
    for j in range(len(S) - 1, -1, -1):
        B = B + S[j]
    print(B)

    Output

    DRAWER REWARD
    REWARD
    
  31. T is DRAWER again, and R is REWARD. The backwards loop also gives REWARD: in Python its end is -1, one past 0, so that position 0 is included. In a trace, write a table with a column for j, one for S[j] and one for the string after the pass:

    j S[j] R after the pass
    0 D D
    1 R RD
    2 A ARD
    3 W WARD
    4 E EWARD
    5 R REWARD
  32. Palindromes

    A palindrome reads the same forwards and backwards, like RADAR or NOON. A program can test it in two ways:

    • build the reverse and compare it with ==;

    • compare each character with its mirror, S[j] with S[len(S) - 1 - j], from the outside in.

    For the mirror test, the loop only needs to reach the middle: FOR j = 0 TO int(len(S) / 2) - 1.

  33. S = "RADAR"
    R = ""
    for j in range(len(S)):
        R = S[j] + R
    print(R == S)
    W = "ROTOR BLADE"
    same = 0
    for j in range(len(W) // 2):
        if W[j] == W[len(W) - 1 - j]:
            same = same + 1
    print(len(W) // 2, same)

    Output

    True
    5 0
    
  34. RADAR reversed is RADAR, so the first test prints True. ROTOR BLADE has 11 characters, so the mirror loop checks 5 pairs: R E, O D, T A, O L and R B. None of them match, so same is 0: the test found that it is not a palindrome. With an odd length, the middle character is its own mirror and is never checked, which is fine.

  35. Put it together: this program reads a word, reverses it, and says whether it is a palindrome.

    INPUT S
    R = ""
    FOR j = 0 TO len(S) - 1
       R = S[j] + R
    NEXT j
    IF R == S THEN
       OUTPUT R, "YES"
    ELSE
       OUTPUT R, "NO"
    END IF

    The input is one word in capital letters. It is saved as assets/l2.py.

  36. Try it
    S = input()
    R = ""
    for j in range(len(S)):
        R = S[j] + R
    if R == S:
        print(R, "YES")
    else:
        print(R, "NO")
  37. Trace it by hand on each input before you run it from this unit's folder:

    python assets/l2.py < assets/l2/1.in
  38. A one-letter word (assets/l2/3.in) is always a palindrome. ABCA (assets/l2/5.in) starts and ends with the same letter but is not a palindrome: every pair of mirrors must match, not only the outside pair.

  39. Lesson 3 — Moving pieces, comparing letters, shrinking strings

    Division: Junior and above.

    The hardest string questions put a piece inside a loop, compare letters, or chop a string shorter on every pass. Each one is traced the same way: work out the piece first, write it down, and only then compare or join it.

  40. A piece that moves along the string

    S[j:j + 1] is positions j through j + 1: a piece of two characters that moves along as j grows. The loop must stop at len(S) - 2, so that j + 1 is still inside the string.

    S = "ASSESSMENT"
    c = 0
    FOR j = 0 TO len(S) - 2
       IF S[j:j + 1] == "SS" THEN
          c = c + 1
       END IF
    NEXT j
    OUTPUT c

    In Python the same piece is S[j:j + 2].

  41. S = "ASSESSMENT"
    c = 0
    for j in range(len(S) - 1):
        if S[j:j + 2] == "SS":
            c = c + 1
    print(c)
    print(S[3:3 + 2], S[3:4])

    Output

    2
    ES E
    

    SS sits at positions 1 and 4, so c is 2. At j = 3, ACSL's S[3:4] is the two letters ES, but Python's S[3:4] is only E. A reader who uses Python's rule never finds a two-letter match and answers 0.

  42. Endings: the last k letters

    S[k:] is the last k characters, so a loop over k looks at longer and longer endings of the word.

    FOR k = 1 TO len(S)
       ...S[k:]...
    NEXT k

    In Python the ending is S[len(S) - k:].

  43. S = "TIGER"
    for k in range(1, len(S) + 1):
        print(k, S[len(S) - k:], S[:k])

    Output

    1 R T
    2 ER TI
    3 GER TIG
    4 IGER TIGE
    5 TIGER TIGER
    

    The endings are R, ER, GER, IGER and TIGER, and the beginnings S[:k] grow from the other side. A program that compares S[:k] with S[k:] is asking whether the word starts and ends with the same k letters.

  44. Comparing letters

    < and > compare two capital letters by their order in the alphabet: "A" < "B" is true, and "M" > "Z" is false. Every string in this unit that is compared this way uses capital letters only. (In Python every capital letter comes before every small letter, so "Z" < "a" is true: never mix the two when you compare.)

    Comparing letters lets a program find the letter latest in the alphabet, just as it finds the largest number.

  45. S = "SQUIRREL"
    big = S[0]
    where = 0
    early = 0
    for j in range(len(S)):
        if S[j] > big:
            big = S[j]
            where = j
        if S[j] < "M":
            early = early + 1
    print(big, where, early)

    Output

    U 2 3
    

    U, at position 2, is the latest letter, so the answer starts U 2. The letters before M in the alphabet are I, E and L, so early is 3. With >, a later copy of the same letter never replaces the first one; with >= it would.

  46. Putting two letters in order

    Compare two letters and join them smaller first:

    a = S[j]
    b = S[j + 1]
    IF a > b THEN
       T = T + b + a
    ELSE
       T = T + a + b
    END IF
  47. S = "TOPCAT"
    T = ""
    for j in range(0, len(S), 2):
        a = S[j]
        b = S[j + 1]
        if a > b:
            T = T + b + a
        else:
            T = T + a + b
    print(T)

    Output

    OTCPAT
    

    The pairs are TO, PC and AT. T comes after O, so the first pair becomes OT; PC becomes CP; AT is already in order. The result is OTCPAT.

  48. Shrinking a string with WHILE

    A WHILE loop can chop a string shorter on every pass until it is used up.

    • S = S[len(S) - 1:] keeps the last len(S) - 1 characters: it drops the first character.

    • S = S[:len(S) - 1] keeps the first len(S) - 1 characters: it drops the last character.

    Remember to compute len(S) for the string as it is now, since it shrinks on every pass.

  49. S = "PLANET"
    while len(S) > 0:
        print(S)
        S = S[len(S) - (len(S) - 1):]

    Output

    PLANET
    LANET
    ANET
    NET
    ET
    T
    

    The lines are PLANET, LANET, ANET, NET, ET and T. On the pass that prints T, len(S) - 1 is 0, so the next S is the last 0 characters: the empty string, and the loop stops. The Python piece S[len(S) - (len(S) - 1):] is the translation rule applied as it is; it works out to S[1:].

  50. Put it together: this program reads a word S and a shorter pattern P. It counts how many times P appears in S, overlaps included, and outputs the count and the first position where P starts (or -1 if it never does).

  51. INPUT S
    INPUT P
    m = len(P)
    c = 0
    first = -1
    FOR j = 0 TO len(S) - m
       IF S[j:j + m - 1] == P THEN
          c = c + 1
          IF first == -1 THEN
             first = j
          END IF
       END IF
    NEXT j
    OUTPUT c, first

    The first input line holds the word and the second holds the pattern, both in capital letters. It is saved as assets/l3.py.

  52. Try it
    S = input()
    P = input()
    m = len(P)
    c = 0
    first = -1
    for j in range(len(S) - m + 1):
        if S[j:j + m] == P:
            c = c + 1
            if first == -1:
                first = j
    print(c, first)
  53. Run it from this unit's folder:

    python assets/l3.py < assets/l3/1.in
  54. The ACSL piece S[j:j + m - 1] has (j + m - 1) - j + 1, which is m, characters: exactly the length of the pattern. In BANANA the pattern ANA is found at positions 1 and 3, which overlap (assets/l3/1.in). When the pattern is longer than the word (assets/l3/5.in), the loop FOR j = 0 TO -1 runs zero times, so the output is 0 -1.

    • In ACSL, S[:n] is the first n characters, S[n:] the last n, and S[a:b] positions a through b.

    • In Python those are S[:n], S[len(S) - n:] and S[a:b + 1].

    • Joining each character on the end copies a string; joining on the front reverses it.

    • A palindrome reads the same forwards and backwards.

    • Read ACSL with ACSL's rules: the Password Machine prints ANTLEPEL 8, not PHANTLEEL 9.

    You can now trace an ACSL program that works on strings and write its exact output.