2019 FRQ 3 - Delimiters

7 min read
  • Delimiter Balance Runner
  • AP Exam Readiness Quiz -- 5 Questions
  • Official Scoring Rubric -- 9 Points Total
  • 2019 AP CSA FRQ #3 -- Delimiters

    Master ArrayList manipulation through interactive challenges on delimiter parsing & balancing

    What You Will Learn
    This FRQ tests your ability to work with ArrayList<String>, iterate through arrays, compare strings with .equals(), and track state using accumulators -- all core AP CSA concepts.
    The Problem
    Encoded strings use delimiters -- paired markers like (/) or <B>/</B>. You will write two methods: one to extract delimiters from a token array, and one to check if they are balanced.
    Real-World Connection
    Every web browser uses delimiter balancing to parse HTML. Every compiler checks parentheses in your code. This FRQ mirrors how real software works.

    Examples of Delimiters

    Type Open Close Valid Example Invalid Example
    Math ( ) (x + y) * 5 (x + (y) – more opens than closes
    HTML <B> </B> <B>bold</B> <B>bold</UB> – mismatched close

    The Delimiters Class

    Code Runner Challenge

    Part (a) -- Complete the getDelimitersList method

    View IPYNB Source
    public class Delimiters {
        private String openDel;
        private String closeDel;
        public Delimiters(String open, String close) { ... }
        public ArrayList<String> getDelimitersList(String[] tokens) { /* Part A */ }
        public boolean isBalanced(ArrayList<String> delimiters)     { /* Part B */ }
    }
    
    Lines: 1 Characters: 0
    Output
    Click "Run" in code control panel to see output ...

    Part A – getDelimitersList

    Given a String[] tokens array containing text and delimiters, return an ArrayList<String> with only the open and close delimiters, in their original order.

    Example: openDel = "(", closeDel = ")", tokens = ["(", "x + y", ")", "* 5"] Returns ["(", ")"]

    Example: openDel = "<q>", closeDel = "</q>", tokens = ["<q>", "yy", "</q>", "zz", "</q>"] Returns ["<q>", "</q>", "</q>"]


    Popcorn Hack 1: String Comparison

    Inside getDelimitersList, you compare each token to openDel and closeDel. Which comparison is correct?

    if (tokens[i] == openDel || tokens[i] == closeDel)
    if (tokens[i].equals(openDel) || tokens[i].equals(closeDel))
    if (tokens[i].compareTo(openDel) && tokens[i].compareTo(closeDel))
    if (tokens.contains(openDel) || tokens.contains(closeDel))

    Code Runner Challenge

    Part (B) -- Complete the isBalanced method

    View IPYNB Source
    // CODE_RUNNER: Part (a) -- Complete the getDelimitersList method
    
    import java.util.ArrayList;
    
    public class Delimiters {
        private String openDel;
        private String closeDel;
    
        public Delimiters(String open, String close) {
            openDel = open;
            closeDel = close;
        }
    
        /** Part (a): Returns an ArrayList of delimiters from the array tokens */
        public ArrayList<String> getDelimitersList(String[] tokens) {
            ArrayList<String> result = new ArrayList<String>();
            for (int i = 0; i < tokens.length; i++) {
                if (tokens[i].equals(openDel) || tokens[i].equals(closeDel)) {
                    result.add(tokens[i]);
                }
            }
            return result;
        }
    
        public static void main(String[] args) {
            // Test with parentheses
            Delimiters d1 = new Delimiters("(", ")");
            String[] tokens1 = {"(", "x + y", ")", "* 5"};
            System.out.println("Test 1: " + d1.getDelimitersList(tokens1));
            // Expected: [(, )]
    
            // Test with HTML tags
            Delimiters d2 = new Delimiters("<q>", "</q>");
            String[] tokens2 = {"<q>", "yy", "</q>", "zz", "</q>"};
            System.out.println("Test 2: " + d2.getDelimitersList(tokens2));
            // Expected: [<q>, </q>, </q>]
        }
    }
    
    Delimiters.main(null);
    
    Lines: 1 Characters: 0
    Output
    Click "Run" in code control panel to see output ...

    Part B – isBalanced

    Returns true when delimiters are balanced. Two rules:

    1. At no point while scanning left-to-right are there more close delimiters than open delimiters.
    2. Total open delimiters equals total close delimiters.

    Drag and Drop: Order the isBalanced Algorithm

    Build the Algorithm

    Drag each step into the correct order to implement isBalanced

    Available Steps

    If token equals closeDel, decrement openCount
    Initialize openCount = 0
    After loop: return openCount == 0
    If openCount < 0, return false immediately
    If token equals openDel, increment openCount

    Correct Order

    Step 1
    Drop here
    Step 2
    Drop here
    Step 3
    Drop here
    Step 4
    Drop here
    Step 5
    Drop here

    Popcorn Hack 2: The Order Trap

    Why can you not simply count all opens and all closes separately and check if they are equal?

    It would be too slow -- O(n^2)
    ) ( would pass -- closes before opens
    You cannot count Strings in Java
    ArrayList does not support counting

    Popcorn Hack 3: Loop and Return Logic

    In isBalanced, what should happen inside the loop when openCount becomes negative?

    A) Set a boolean flag isValid = false and continue the loop
    B) Return false immediately -- no need to check further
    C) Reset openCount to 0 and keep going
    D) Throw an IllegalStateException

    Live Delimiter Visualizer

    Watch isBalanced execute step-by-step. Select a delimiter sequence and step through the algorithm to see how the counter changes at each position.

    isBalanced -- Step-by-Step Visualizer

    See how the counter rises and falls as we scan delimiters left-to-right

    Delimiter Sequence
    Open Counter
    0
    openCount
    Press Step to begin walking through the algorithm.
    // CODE_RUNNER: Part (B) -- Complete the isBalanced method
    
    import java.util.ArrayList;
    
    public class Delimiters {
        private String openDel;
        private String closeDel;
    
        public Delimiters(String open, String close) {
            openDel = open;
            closeDel = close;
        }
    
        /** Part (a): Extract delimiters from tokens */
        public ArrayList<String> getDelimitersList(String[] tokens) {
            ArrayList<String> result = new ArrayList<String>();
            for (int i = 0; i < tokens.length; i++) {
                if (tokens[i].equals(openDel) || tokens[i].equals(closeDel)) {
                    result.add(tokens[i]);
                }
            }
            return result;
        }
    
        /** Part (b): Check if delimiters are balanced */
        public boolean isBalanced(ArrayList<String> delimiters) {
            int openCount = 0;
            int closeCount = 0;
            for (int i = 0; i < delimiters.size(); i++) {
                if (delimiters.get(i).equals(openDel)) {
                    openCount++;
                } else {
                    closeCount++;
                }
                // Rule 1: At no point should closes exceed opens
                if (closeCount > openCount) {
                    return false;
                }
            }
            // Rule 2: Final count of opens must equal closes
            return openCount == closeCount;
        }
    
        public static void main(String[] args) {
            Delimiters d = new Delimiters("<sup>", "</sup>");
    
            // Test 1: Balanced -- expected true
            ArrayList<String> t1 = new ArrayList<>();
            t1.add("<sup>"); t1.add("<sup>"); t1.add("</sup>");
            t1.add("<sup>"); t1.add("</sup>"); t1.add("</sup>");
            System.out.println("Test 1 (expect true):  " + d.isBalanced(t1));
    
            // Test 2: Close before open -- expected false (Rule 1)
            ArrayList<String> t2 = new ArrayList<>();
            t2.add("<sup>"); t2.add("</sup>"); t2.add("</sup>"); t2.add("<sup>");
            System.out.println("Test 2 (expect false): " + d.isBalanced(t2));
    
            // Test 3: Single close -- expected false (Rule 1)
            ArrayList<String> t3 = new ArrayList<>();
            t3.add("</sup>");
            System.out.println("Test 3 (expect false): " + d.isBalanced(t3));
    
            // Test 4: Unequal counts -- expected false (Rule 2)
            ArrayList<String> t4 = new ArrayList<>();
            t4.add("<sup>"); t4.add("<sup>"); t4.add("</sup>");
            System.out.println("Test 4 (expect false): " + d.isBalanced(t4));
        }
    }
    
    Delimiters.main(null);
    

    Delimiter Balance Runner

    Use the arrow keys (or A/D) to move your collector left and right. Delimiters fall from the sky. You must catch them in the correct order to build a balanced sequence. Catching a delimiter that breaks the balance costs a life. The sequence must be balanced when the wave ends – if not, you lose a life and retry. Survive all waves to win.

    This game reinforces the exact logic of isBalanced: you must track open vs close counts in real time, and order matters.

    Delimiter Balance Runner

    Move with arrow keys or A/D. Catch delimiters in the right order to build balanced sequences.

    Score: 0
    Lives: 3
    Wave: 1
    openCount: 0
    Sequence: --

    Final Knowledge Check

    AP Exam Readiness Quiz -- 5 Questions

    Test your understanding of everything in this FRQ

    Your Score
    0/5

    AP Scoring Guidelines

    Official Scoring Rubric -- 9 Points Total

    Use this to self-grade your solution

    Part (a) -- getDelimitersList (4 points)
    +1 Creates ArrayList<String>
    +1 Accesses all elements in array tokens (no bounds errors)
    +1 Compares strings with both instance variables using .equals() in a loop
    +1 Adds delimiters into ArrayList in original order
    Part (b) -- isBalanced (5 points)
    +1 Initializes accumulator(s)
    +1 Accesses all elements in ArrayList delimiters (no bounds errors)
    +1 Compares strings with instance variables and updates accumulator(s)
    +1 Identifies and returns appropriate boolean for one rule
    +1 Identifies and returns appropriate boolean values for all cases
    Common Mistakes That Lose Points
    Using == for string comparison instead of .equals()
    Accessing array elements as ArrayList: tokens.get(i) instead of tokens[i]
    Not returning false inside the loop when closes exceed opens
    Initializing the accumulator inside the loop instead of before it

    Course Timeline