Regular Expression Notation
Regular expression notation describes patterns that text may follow.
Why Strings Need a Closer Look
A string is one of the most common kinds of data a program handles. Names, messages, file paths, and user input are all strings. When you write a string literal or read text from a user, you usually think of the text as a single value. Working with strings effectively, however, requires a more detailed picture of what that value is and how a program can reach into it. This article introduces that picture. It is the starting point for the rest of the chapter, because the three ways of working with string contents that the section introduces all depend on it.
What do you think happens?
Before reading further, what would make a regular expression easy for a future reader to understand, and what would make it impossible?
A String as a Row of Slots
The central idea is that a string is an ordered sequence of characters. Order is what makes a string more than a bag of letters. The first character comes before the second, the second before the third, and so on. Because of this order, every character in a string has a definite place that can be referred to.
A convenient way to picture this is as a row of numbered slots, with one character per slot. The slot numbers are the positions. Reading a character means naming its position. Searching, pattern matching, and iteration all reduce to ways of visiting or comparing these slots in some order. The picture below shows the general model rather than any particular machine's layout: each slot holds one character, the slots are arranged in a definite order, and the position number is what lets a program say which character it means. The model applies to any string, whatever its length.
Numbering Positions from Zero
Once a string is seen as a sequence, the next question is how positions are counted. Many programming languages, including C++, number the characters of a string starting from 0 rather than 1. The first character is at position 0, and a string with n characters has positions running from 0 up to $n-1$. The last character is therefore at position length minus 1, not at position length. This convention affects nearly every operation that touches a string's contents, so it is worth fixing in your mind early.
Locating characters in the string regex
The string regex has 5 characters. Which character is at position 0, which is at position 3, and which position holds the last character?
- 1Count the characters
The string regex contains r, e, g, e, x, so its length is 5.
- 2Apply the numbering rule
Positions run from 0 up to $n-1$, which here is $5-1 = 4$.
- 3Read position 0
The first character, r, is at position 0.
- 4Read position 3
Counting slots from zero, position 0 is r, 1 is e, 2 is g, and 3 is the second e.
- 5Locate the last character
The last character x is at position 4, which is length minus 1.
Position 0 holds r, position 3 holds e, and the last character x sits at position 4.
Off-by-One Errors with Positions
Common mistake Treating the last position as the length of the string
What it looks likeFor the string regex, assuming the last position is 5 because the string has 5 characters.
Why it failsPositions are numbered from 0, so a string of length n has its last position at n minus 1.
Do this insteadFor a string of length 5, the last valid position is 4. Subtract 1 from the length when locating the end.
Common mistake Counting positions from 1
What it looks likeReading position 3 of regex as the third character, which is g.
Why it failsPosition 3 is the fourth slot counting from zero, which holds the second e.
Do this insteadStart counting at 0, so position 0 is the first character, position 1 is the second, and so on.
Common mistake Assuming that a position number identifies a character by its value rather than its slot, so that the letter e can be referred to without saying which one.
Three Ways of Working with Contents
The section lists three topics that build on the sequence model. Each answers a different question about a string. Searching asks where a piece of text occurs. Regular expression notation describes patterns that text may follow. Iterators provide a systematic way to move through a string one element at a time. All three rely on the fact that positions are ordered, which is why they are best learned in the order the chapter presents them.
- Searching: find where a piece of text occurs within a string.
- Regular expression notation: describe a pattern that text may follow, rather than one exact piece of text.
- Iterators: move through a string systematically, one element at a time, using positions in order.
The Default Regex Notation
Every regular expression is written in some notation, meaning a set of rules about which symbols mean what. The default regular expression notation described in this section is that of ECMAScript. Unless you choose otherwise, the rules you read are the ECMAScript rules.
The notation is not fixed to that single default. The source states that it can be adjusted to match various standards. The same general idea of a pattern can therefore be written under different notational conventions, and you select the convention your situation requires. The key point is that a pattern's text alone does not fix its meaning; the notation in force does. When you read or share a pattern, the notation it was written for should be clear.
Why Regular Expressions Become Write-Only
Many learners conclude that regular expressions are a kind of code that only its author can ever read. A single line can hold a dense cluster of symbols, and once written it seems to resist later review. The source names this belief directly: regular expressions can easily become a write-only language. The belief is partly true because a regular expression packs a lot of meaning into very few characters. A reader has to recognize each symbol, understand how symbols combine, and keep track of which part of the pattern each character belongs to. When a pattern grows without discipline, these combinations become hard to follow, even for the person who wrote them.
Restraint is the practical correction. Do not write the most compact pattern that happens to work. Write the pattern that a later reader can verify at a glance. The source gives this as a tip for using regular expressions effectively, and it applies to short patterns and long ones alike.
Lazy Patterns with the Question Mark
The source gives one specific modifier: the question mark ?. Placing ? in a pattern makes that pattern lazy. A lazy pattern matches as little text as it can while still satisfying the pattern, as opposed to matching as much as possible. Because the modifier is a single symbol, the change it makes is easy to overlook when reading a pattern. Always check for it when you are predicting what a pattern will match.
What do you think happens?
If a pattern would otherwise match a long stretch of text, would the lazy version match a longer or a shorter stretch?
Reveal answer
A shorter stretch, because a lazy pattern matches as little text as it can while still satisfying the pattern.
The modifier ? changes the pattern's preference from matching as much as possible to matching as little as possible.
The comparison below uses an original example to make the difference visible. Suppose the input text is the HTML fragment <b>bold</b>. The greedy pattern <.+> begins at the first less-than sign and extends as far as it can, so it matches the whole fragment, through the final greater-than sign. The lazy pattern <.+?> begins at the same place but stops at the first greater-than sign it reaches, so it matches only the opening tag <b>. The two patterns differ by a single symbol, but they stop in different places.
Expressing a Subpattern with \i
The notation includes \i, which the source describes as allowing you to express a subpattern in terms of a previous subpattern. Instead of spelling out the same subpattern again, you refer back to an earlier one. The result reads as a composition of named ideas rather than one long sequence of symbols. The source does not provide further detail about \i in this section, so this article does not describe its exact behavior beyond that statement.
What matters for good practice is the principle behind \i: reuse an earlier subpattern where doing so keeps the whole pattern clearer, and leave it out where it makes the pattern harder to follow. Recognize \i as a reference to an earlier part of the same pattern, and check the surrounding notation before relying on its exact behavior.
Iterating over a Stream with a Regex Iterator
Regular expressions are not only used to test a single string. The source explains that you can use regex iterators to iterate over a stream while looking for a pattern. The pattern is applied repeatedly as you move through a stream of text, and each match the iterator finds is a point where your program can act. The sequence below is an original illustration of that process. The source names the regex iterator and its purpose but does not spell out every step, so treat it as a way to picture the work rather than a full specification of the library.
Notice what the iterator does at each stage. It keeps a place in the stream, so each search starts from where the previous match ended rather than from the beginning again. When it reaches the end of the stream with no further matches, the process is finished. Each match is an opportunity for your program to act, which is what makes an iterator useful for repeated passes over text rather than a single lookup.
Practice Reading Positions and Patterns
For the string regex, state which character sits at position 2 and which position holds the last character. Then explain whether the lazy pattern <.+?> would match more or less text than the greedy pattern <.+> on the input <b>bold</b>.
Hints
- Count from 0: the first character is at position 0.
- The last position is the length of the string minus 1.
- A lazy pattern matches as little text as it can while still satisfying the pattern.
Key Takeaways
- A string is an ordered sequence of characters, and each character has a definite position that can be referred to.
- Positions are numbered from 0, so the first character is at position 0 and the last is at position length minus 1.
- Searching, regular expression notation, and iterators are the three ways of working with string contents, and all of them depend on the ordered-sequence model.
- The default regular expression notation is ECMAScript and can be adjusted to match other standards; the ? modifier makes a pattern lazy, and \i lets one subpattern be expressed in terms of a previous one.
- Regex iterators move through a stream while searching for a pattern, and restraint in writing patterns keeps them readable for later reviewers.