R Programming Quiz: Regular Expressions
10 questions · exam conditions
0:00
Regular ExpressionsQuestion 1 of 10

Consider the following R code:

files <- c("sales.csv", "salesXcsv", "archive.csv.bak", ".csv")

grep("\\.csv$", files)

Which value is returned?

c(1, 4)
c(1, 2, 4)
c(1, 3, 4)
c(1)
← Back to quizzes

R Programming Quiz

R Programming Quiz: Regular Expressions

Practice Regular Expressions in R Programming with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.

What this quiz covers

This quiz focuses on Regular Expressions, giving you a quick way to practice the rules, question types, and explanations that matter most for R Programming.

How to use this quiz

Try each quiz question before looking at the correct answer. Use the explanations to review missed ideas, then come back to similar questions until the pattern feels familiar.

All questions

Question 1

Consider the following R code:

files <- c("sales.csv", "salesXcsv", "archive.csv.bak", ".csv")

grep("\\.csv$", files)

Which value is returned?

  1. c(1, 4) (correct answer)
  2. c(1, 2, 4)
  3. c(1, 3, 4)
  4. c(1)
Explanation: When working with grep() and regular expressions in R, your job is to mentally "run" the pattern against each string in the vector and track which positions match. The pattern \\.csv$ breaks down into two parts: \\. matches a literal dot (the double backslash escapes the dot, since in regex a bare . means "any character"), and csv$ matches the exact characters "csv" at the end of the string. So you're looking for strings that end with .csv — nothing after it. Checking each element of files:
  • "sales.csv" — ends with .csv ✓ → position 1
  • "salesXcsv" — has no dot before "csv", so \\. fails ✗ → skip
  • "archive.csv.bak" — has .csv but it's followed by .bak, so $ fails ✗ → skip
  • ".csv" — this is just a dot followed by "csv" at the end ✓ → position 4
That gives you positions c(1, 4), confirming A is correct. Choice B is wrong because it includes position 2 ("salesXcsv"), which would only match if the dot were unescaped (.csv$ would treat X as "any character"). Choice C incorrectly includes position 3 ("archive.csv.bak"), confusing "contains .csv" with "ends with .csv" — the $ anchor rules it out. Choice D is wrong because it misses position 4, overlooking that ".csv" is a valid match. A useful habit: whenever you see \\. in an R regex string, remember the first backslash escapes the second, so \\. reaches the regex engine as \., meaning a literal dot. Pattern-test each string mentally before selecting your answer.

Question 2

An analyst runs the following R code using Perl-compatible regular expressions:

x <- c("cat scan", "catapult", "a cat!", "Cat")

grepl("\\bcat\\b", x, perl = TRUE)

Which logical vector is returned?

  1. c(FALSE, FALSE, TRUE, TRUE)
  2. c(TRUE, TRUE, TRUE, FALSE)
  3. c(TRUE, FALSE, FALSE, TRUE)
  4. c(TRUE, FALSE, TRUE, FALSE) (correct answer)
Explanation: When working with regular expressions in R, the key concept here is word boundaries. The pattern \\bcat\\b uses \\b to mark the start and end of a word boundary, meaning it matches "cat" only when it appears as a standalone word — not as part of a longer word like "catapult." Walking through the vector element by element confirms answer D. In "cat scan", "cat" appears surrounded by a space and the string start, so it's a complete word — TRUE. In "catapult", "cat" is embedded inside the word with no boundary after it, so the match fails — FALSE. In "a cat!", "cat" is bounded by a space and a punctuation mark; importantly, \\b treats punctuation as a non-word character, so a boundary exists — TRUE. Finally, "Cat" uses a capital C, and grepl is case-sensitive by default with no ignore.case = TRUE argument, so it doesn't match — FALSE. This gives you c(TRUE, FALSE, TRUE, FALSE). Answer A is wrong because it treats "cat scan" as a non-match, perhaps confusing the space as a barrier. Answer B incorrectly matches "catapult" and "catapult" while missing the case sensitivity issue — it reflects someone who ignored both \\b boundaries and case sensitivity. Answer C flips the results for "cat scan" and "a cat!", suggesting a misreading of where word boundaries apply. As a study tip: whenever you see \\b in a regex pattern, immediately ask yourself two things — is the target word truly isolated by non-word characters, and is case sensitivity accounted for?

Question 3

An analyst needs a regular expression for dates whose year begins with 19 or 20, whose month is from 01 through 12, and whose day field is any two digits. The entire string must follow this format. Which regular expression meets these requirements?

  1. ^(19|20)[0-9]{2}-(0[1-9]|1[0-2])-[0-9]{2}$ (correct answer)
  2. ^19|20[0-9]{2}-(0[1-9]|1[0-2])-[0-9]{2}$
  3. ^(19|20)[0-9]{2}-[0-1][0-9]-[0-9]{2}$
  4. ^(19|20)[0-9]{2}-(0[1-9]|1[0-2])-[0-9]+$
Explanation: When working with regular expressions for date validation, you need to think carefully about two things: grouping with alternation and range precision. Regex alternation (|) has very low precedence, meaning it applies to everything on either side unless you explicitly wrap it in parentheses. Option A — ^(19|20)[0-9]{2}-(0[1-9]|1[0-2])-[0-9]{2}$ — is correct because every requirement is precisely handled. The (19|20) group correctly captures only those two century prefixes. The month pattern (0[1-9]|1[0-2]) matches exactly 01–09 and 10–12, excluding invalid months like 00 or 13. The day field [0-9]{2} allows any two-digit day, and the anchors ^ and $ ensure the entire string conforms to the format. Option B fails because the alternation ^19|20[0-9]{2}-... is ungrouped — without parentheses, this matches either the string starting with 19, or a string starting with 20 followed by the rest of the pattern. The ^ anchor only applies to the 19 branch, breaking the logic entirely. Option C uses [0-1][0-9] for the month, which looks reasonable but actually allows invalid months like 00 and 19. The character class [0-1] permits 0 or 1, and [0-9] permits any digit, so months 00, 19, 18, etc., would all pass. Option D is close but uses [0-9]+ for the day field — the + quantifier means one or more digits, so day values like 1 or 123456 would incorrectly match. The key study tip: always wrap alternation options in parentheses when they're meant to apply together, and prefer explicit character class ranges over broad ones when validating constrained fields like months.

Question 4

A programmer wants to accept both American and British spellings, but no additional characters. What does the following code return?

x <- c("color", "colour", "colouur", "colors")

grepl("^colou?r$", x)

  1. c(TRUE, TRUE, TRUE, FALSE)
  2. c(FALSE, TRUE, FALSE, FALSE)
  3. c(TRUE, TRUE, FALSE, FALSE) (correct answer)
  4. c(TRUE, FALSE, FALSE, TRUE)
Explanation: When working with regular expressions in R, your job is to mentally "walk" the pattern across each string character by character. The pattern ^colou?r$ breaks down like this: ^ anchors to the start, colo matches literally, u? means zero or one "u" (not zero or more), r matches literally, and $ anchors to the end. Applying this to each element of x confirms C is correct. "color" matches because u? allows zero u's — the pattern becomes color with ^ and $ enforcing exact boundaries. "colour" matches because u? allows exactly one u. "colouur" fails because it contains two u's — u? only permits one, and the extra u breaks the match before reaching r$. "colors" fails because the trailing s violates the $ anchor; nothing may follow r. Looking at the wrong answers: A incorrectly marks "colouur" as TRUE, confusing u? (zero or one) with u* (zero or more). B marks only "colour" as TRUE, treating u? as requiring exactly one u — that's the behavior of plain u with no quantifier. D marks "color" and "colors" as TRUE, which ignores the $ anchor entirely and misreads how optional quantifiers work. A reliable study tip: memorize the three core quantifiers together — ? (0 or 1), * (0 or more), + (1 or more) — and always check whether ^ and $ anchors are present, since they're the most common source of confusion in regex matching questions.

Question 5

Consider the following R code:

x <- c("ID-7", "XID-7", "ID-72", "ID-")

grepl("^ID-[0-9]$", x)

Which logical vector is returned?

  1. c(TRUE, FALSE, FALSE, FALSE) (correct answer)
  2. c(TRUE, TRUE, FALSE, FALSE)
  3. c(TRUE, FALSE, TRUE, FALSE)
  4. c(TRUE, TRUE, TRUE, FALSE)
Explanation: When working with grepl() in R, you're applying a regular expression pattern to each element of a vector and getting back a logical vector of TRUE/FALSE matches. The key is dissecting the pattern character by character. The pattern "^ID-[0-9]$" breaks down as follows: ^ anchors the match to the start of the string, ID- matches those literal characters, [0-9] matches exactly one digit, and $ anchors to the end of the string. So the pattern demands the entire string be exactly ID- followed by a single digit — nothing more, nothing less. Now check each element of x: "ID-7" starts with ID-, has exactly one digit (7), and ends there — this matches, giving TRUE. "XID-7" fails immediately because ^ requires the string to start with ID-, but it starts with X. "ID-72" contains two digits after the dash, so $ fails since the string doesn't end after the first digit. "ID-" has no digit at all, so [0-9] cannot match. This gives c(TRUE, FALSE, FALSE, FALSE), confirming A. Choice B is wrong because it treats "XID-7" as a match, ignoring the ^ anchor. Choice C incorrectly matches "ID-72", forgetting that $ requires the string to end immediately after one digit. Choice D compounds both mistakes by misreading both anchors. A useful study tip: always read regex anchors (^ and $) first — they define the boundaries of what's allowed and are the most common source of tricky wrong answers on pattern-matching questions.

Question 6

What is assigned to result by the following R code?

x <- "acct 48 paid 125"

m <- regexpr("[0-9]+", x)

result <- regmatches(x, m)

  1. The character value "48" (correct answer)
  2. The character vector c("48", "125")
  3. The character value "4"
  4. The integer vector c(6, 2)
Explanation: When working with regular expressions in R, it's crucial to distinguish between functions that find one match versus all matches — that distinction is exactly what this question tests. Here, regexpr() scans the string "acct 48 paid 125" and returns the position and length of only the first match of the pattern [0-9]+ (one or more digits). It finds "48" starting at position 6, with a match length of 2. The result m is essentially metadata — not the extracted text itself. Then regmatches(x, m) uses that metadata to pull the actual substring from x, returning the character value "48". So A is correct. Choice B is the most tempting trap. If you used gregexpr() instead of regexpr(), you'd get all matches, and regmatches() would then return c("48", "125"). The extra g in gregexpr stands for "global" — a critical difference. Using regexpr() stops after the first match, so B is wrong. Choice C suggests "4" — a single digit — which would only be correct if the pattern were [0-9] (exactly one digit) rather than [0-9]+ (one or more digits). The + quantifier greedily matches the entire sequence "48", not just "4". Choice D describes what regexpr() itself returns (position 6, length 2) — raw match metadata — not what regmatches() extracts. Don't confuse the intermediate object with the final result. Your study tip: memorize the regexpr vs. gregexpr pairing — one match vs. all matches — because R exam questions frequently exploit exactly this distinction.

Question 7

What is the value of parts after this code runs?

x <- "Date=2025-07-09"

m <- regexec("Date=([0-9]{4})-([0-9]{2})-([0-9]{2})", x)

parts <- regmatches(x, m)[[1]]

  1. c("2025", "07", "09")
  2. c("Date=2025-07-09", "2025", "07", "09") (correct answer)
  3. c("Date=", "2025", "07", "09")
  4. c("Date=2025-07-09", "2025-07-09")
Explanation: When working with regexec() and regmatches() in R, the key concept to understand is how capture groups interact with the full match — because R returns both, and their order matters. regexec() applies a regex with capture groups and records the positions of every match: first the entire pattern match, then each parenthesized group in left-to-right order. When you pass that result to regmatches(), it extracts the actual substrings in that same order. So for the pattern "Date=([0-9]{4})-([0-9]{2})-([0-9]{2})" applied to "Date=2025-07-09", you get four elements: the full match "Date=2025-07-09", then group 1 "2025", group 2 "07", and group 3 "09". That makes Bc("Date=2025-07-09", "2025", "07", "09") — the correct answer. Choice A omits the full match entirely and only returns the three capture groups, which is what regmatches() would give you if you used regexpr() with perl=TRUE and gregexpr() tricks — not regexec(). Choice C incorrectly includes "Date=" as a standalone element; nothing in the pattern produces that as a separate group. Choice D suggests only two elements, confusing regexec() with regexpr(), which returns just one match position (no group tracking) and would yield only a single string. A reliable study tip: whenever you see regexec() + regmatches(), mentally count 1 + n results, where n is the number of () groups in your pattern. The full match always comes first.

Question 8

Consider this use of gregexpr() and regmatches():

x <- c("A12B3", "none")

result <- regmatches(x, gregexpr("[0-9]+", x))

Which description of result is correct?

  1. c("12", "3", character(0))
  2. list(c("1", "2", "3"), character(0))
  3. list(c("12", "3"), "none")
  4. list(c("12", "3"), character(0)) (correct answer)
Explanation: When working with gregexpr() and regmatches() in R, you need to understand what each function contributes. gregexpr() finds all matches of a pattern within each string and returns their positions. regmatches() then extracts the actual substrings at those positions. Crucially, this combination always returns a list with one element per input string — not a flattened vector. For x <- c("A12B3", "none"), the pattern [0-9]+ matches one or more consecutive digits. In "A12B3", there are two matches: "12" (digits grouped together) and "3" — so the first list element is c("12", "3"). In "none", there are no digit matches at all, so gregexpr() returns -1 with a zero-length match, and regmatches() produces character(0) — an empty character vector. This makes D, list(c("12", "3"), character(0)), correct. A is wrong because it collapses everything into a flat vector, ignoring that regmatches() always returns a list. B makes a subtle but critical error: [0-9]+ uses the + quantifier, meaning it matches runs of digits greedily. "12" is captured as a single token, not split into "1" and "2" individually — that would require [0-9] without the +. C is wrong because "none" contains no digits, so the result is character(0), not the string "none" itself; regmatches() never returns non-matching content by default. A good study habit: always remember that gregexpr() + regmatches() preserves list structure and returns character(0) for no-match elements — it won't silently drop or fill them.

Question 9

Consider the following R expression:

x <- c("AB-CD", "AB12", "123", "")

grepl("[^0-9]+", x)

Which logical vector is returned?

  1. c(TRUE, FALSE, FALSE, FALSE)
  2. c(TRUE, TRUE, FALSE, FALSE) (correct answer)
  3. c(TRUE, TRUE, TRUE, FALSE)
  4. c(FALSE, TRUE, TRUE, TRUE)
Explanation: When working with grepl() and regular expressions in R, your job is to ask: does this pattern appear anywhere in the string? The pattern [^0-9]+ means "one or more characters that are NOT digits." grepl() returns TRUE if that pattern matches anywhere in the string, and FALSE otherwise. Let's walk through each element of x. "AB-CD" contains letters and a hyphen — all non-digits — so the pattern matches: TRUE. "AB12" contains "AB", which are non-digit characters, so the pattern matches there too: TRUE. "123" is entirely digits, meaning there are no non-digit characters at all, so the pattern finds nothing: FALSE. Finally, "" is an empty string — there are no characters of any kind, so the pattern cannot match: FALSE. This gives you c(TRUE, TRUE, FALSE, FALSE), confirming B is correct. Choice A is wrong because it marks "AB12" as FALSE, as if the presence of digits in the string means non-digits don't also exist there — but grepl() only needs one match anywhere in the string. Choice C incorrectly returns TRUE for "123", confusing the negated class [^0-9] with [0-9] — it would be true if you were looking for digits, not non-digits. Choice D reverses the entire logic, suggesting digits-only strings match while non-digit strings don't. A key study tip: whenever you see [^...] in a regex, mentally read it as "NOT these characters." The caret inside square brackets is a negation operator — don't confuse it with ^ used outside brackets to anchor the start of a string.

Question 10

Consider the following code:

x <- c("red-7", "pale-blue", "blue-2", "xred")

grep("^red|blue$", x, value = TRUE)

Which character vector is returned?

  1. c("pale-blue", "blue-2")
  2. c("red-7", "blue-2")
  3. c("red-7", "pale-blue") (correct answer)
  4. c("red-7", "pale-blue", "blue-2")
Explanation: When working with grep() and regular expressions in R, the key skill is parsing the regex pattern carefully — especially how the alternation operator | interacts with anchors like ^ and $. The pattern "^red|blue$" does not mean "starts with red or ends with blue" applied as a single unit. Instead, the | splits the entire expression into two alternatives: ^red (starts with "red") and blue$ (ends with "blue"). R evaluates each string against both alternatives independently. Now apply this to your vector c("red-7", "pale-blue", "blue-2", "xred"):
  • "red-7" → starts with "red" ✓ → match
  • "pale-blue" → ends with "blue" ✓ → match
  • "blue-2" → does not start with "red", and does not end with "blue" (it ends with "2") ✗ → no match
  • "xred" → does not start with "red", does not end with "blue" ✗ → no match
This gives you c("red-7", "pale-blue"), confirming C is correct. Choice A is wrong because it includes "pale-blue" but misses "red-7", and wrongly includes no "starts with red" matches. Choice B incorrectly includes "blue-2", which starts with "blue but doesn't end with it. Choice D includes "blue-2" for the same mistaken reason — treating blue$ as matching anywhere "blue" appears. A reliable tip: when you see ^pattern1|pattern2$, mentally add parentheses as (^pattern1)|(pattern2$) to remind yourself that anchors bind to their immediate sub-expression, not the whole alternation.