{"id":"useful-techniques-quantifiers","slug":"quantifiers","title":"Quantifiers and character classes","text":"Step 3's regexes described texts of a fixed length. Most formats have parts that repeat: a postcode is five digits, a PIN four to six, and a name any number of letters. Two more tools describe them: quantifiers and character classes.\n\n### Quantifiers\n\nA **quantifier** goes after a character, or after a group, and says how many times it may come:\n\n| Quantifier | How many times | Example | Matches | Doesn't match |\n| --- | --- | --- | --- | --- |\n| `*` | any number, zero too | `hm*` | h, hm, hmmm | hmh |\n| `+` | at least once | `go+al` | goal, gooooal | gal |\n| `?` | zero times or once: it's optional | `colou?r` | color, colour | colouur |\n| `{3}` | exactly 3 | `z{3}` | zzz | zz, zzzz |\n| `{2,4}` | from 2 to 4 | `a{2,4}` | aa, aaa, aaaa | a, aaaaa |\n| `{2,}` | 2 or more | `a{2,}` | aa, aaaaaaa | a |\n\nWithout parentheses, a quantifier repeats only the one character right before it. `ha+` is an h followed by one or more a's, and `(ha)+` is ha, once or more:\n\n```java\nSystem.out.println(\"haaa\".matches(\"ha+\"));\nSystem.out.println(\"haaa\".matches(\"(ha)+\"));\nSystem.out.println(\"hahaha\".matches(\"(ha)+\"));\nSystem.out.println(\"gal\".matches(\"go*al\"));\n```\n\n```output\ntrue\nfalse\ntrue\ntrue\n```\n\n`go*al` allows zero o's, so gal matches it, but not `go+al`.\n\n### Character classes\n\nSquare brackets make a **character class**. It matches one character, any of the ones inside: `[aeiou]` is one vowel. A dash between two characters gives a range: `[0-9]` is one digit, `[a-z]` one lowercase letter, `[A-Z]` one capital letter, and `[a-zA-Z]` a letter of either kind.\n\nWith a quantifier, a class describes a whole format. A US ZIP code is five digits, sometimes followed by a dash and four more digits (the ZIP+4 form, such as 10001-4356):\n\n```java\nString zip = \"[0-9]{5}(-[0-9]{4})?\";\nString[] codes = {\"90210\", \"10001-4356\", \"9021\", \"90210-\", \"9021O\"};\nfor (String code : codes) {\n    System.out.println(code + \" \" + code.matches(zip));\n}\n```\n\n```output\n90210 true\n10001-4356 true\n9021 false\n90210- false\n9021O false\n```\n\n- `[0-9]{5}` is five digits.\n- `(-[0-9]{4})?` is a group of a dash and four digits, and the `?` makes the whole group optional. A dash outside square brackets is just a dash.\n- `90210-` has the dash, but not the four digits that must come with it.\n- `9021O` ends with the capital letter O, which isn't in `[0-9]`.\n\n### Times of day\n\nSometimes a class isn't enough by itself, and a group with `|` does the rest. A time on a 24-hour clock is written like 07:45 or 23:05. The hours go from 00 to 23: a 0 or a 1 followed by any digit, or a 2 followed by a digit from 0 to 3. The minutes go from 00 to 59:\n\n```java\nString time = \"([01][0-9]|2[0-3]):[0-5][0-9]\";\nSystem.out.println(\"07:45\".matches(time));\nSystem.out.println(\"23:05\".matches(time));\nSystem.out.println(\"24:00\".matches(time));\nSystem.out.println(\"7:45\".matches(time));\nSystem.out.println(\"12:60\".matches(time));\n```\n\n```output\ntrue\ntrue\nfalse\nfalse\nfalse\n```\n\nThe group holds the two ways to write an hour, and the colon stands for itself.\n\n### A few more\n\n- A dot `.` matches any one character, so `.*` matches any text on one line: `\"b.*\"` matches every one-line string that starts with b.\n- `\\\\d` in a Java String is a shorter way to write `[0-9]`, so `\"\\\\d{5}\"` is five digits.\n- To match a real dot, write `\"\\\\.\"`. Module 16's `split` takes a regular expression too, so `split(\".\")` splits at every character, not just at the dots (a dot means any character), but `split(\"\\\\.\")` splits only at the dots.\n","task":"A hotel's booking codes are three capital letters, a dash and four digits, like `KTX-0415`. Write the method `isBookingCode(String code)`, which returns whether a string has that form, with a regex and `matches`. The check runs:\n\n```java\nSystem.out.println(isBookingCode(\"KTX-0415\"));\nSystem.out.println(isBookingCode(\"ktx-0415\"));\nSystem.out.println(isBookingCode(\"KT-0415\"));\nSystem.out.println(isBookingCode(\"KTX-04155\"));\n```\n\nand expects:\n\n```\ntrue\nfalse\nfalse\nfalse\n```\n","kind":"code","seed":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(isBookingCode(\"KTX-0415\"));\n        System.out.println(isBookingCode(\"ktx-0415\"));\n        System.out.println(isBookingCode(\"KT-0415\"));\n        System.out.println(isBookingCode(\"KTX-04155\"));\n    }\n\n    public static boolean isBookingCode(String code) {\n        return false;\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(isBookingCode(\"KTX-0415\"));\n        System.out.println(isBookingCode(\"ktx-0415\"));\n        System.out.println(isBookingCode(\"KT-0415\"));\n        System.out.println(isBookingCode(\"KTX-04155\"));\n    }\n\n    public static boolean isBookingCode(String code) {\n        return code.matches(\"[A-Z]{3}-[0-9]{4}\");\n    }\n}\n","hints":["A capital letter is `[A-Z]` and a digit `[0-9]`. A quantifier in curly braces says how many of them come in a row.","`return code.matches(\"[A-Z]{3}-[0-9]{4}\");`"],"tests":[{"name":"Test 1","stdin":"","call":"System.out.println(isBookingCode(\"KTX-0415\"));\nSystem.out.println(isBookingCode(\"ktx-0415\"));\nSystem.out.println(isBookingCode(\"KT-0415\"));\nSystem.out.println(isBookingCode(\"KTX-04155\"));\n","expect":"true\nfalse\nfalse\nfalse","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(isBookingCode(\"ABC-0000\") + \" \" + isBookingCode(\"ZZZ-9999\") + \" \" + isBookingCode(\"KTX0415\") + \" \" + isBookingCode(\"KTXX-0415\"));\nSystem.out.println(isBookingCode(\"KTX-041\") + \" \" + isBookingCode(\"K1X-0415\") + \" \" + isBookingCode(\"\") + \" \" + isBookingCode(\"KTX-04a5\"));\n","expect":"true true false false\nfalse false false false","hidden":true},{"name":"System.out.println(isBookingCode(\"KTX-0415 \") + \" \" + isBookingCode(\" KTX-0415\") + \" \" + isBookingCode(\"KTx-0415\") + \" \" + isBookingCode(\"QRS-2026\"));","stdin":"","call":"System.out.println(isBookingCode(\"KTX-0415 \") + \" \" + isBookingCode(\" KTX-0415\") + \" \" + isBookingCode(\"KTx-0415\") + \" \" + isBookingCode(\"QRS-2026\"));\n","expect":"false false false true","hidden":true}],"require":[{"pattern":"\\.matches\\s*\\(","message":"Check the form with `matches` and a regular expression."}],"forbid":[{"pattern":"\\bcharAt\\s*\\(","message":"Let the regex check every character: one call to `matches` is enough."}],"more":[{"task":"What does this program print?\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        String pin = \"[0-9]{4,6}\";\n        System.out.println(\"0815\".matches(pin) + \" \" + \"123\".matches(pin));\n        System.out.println(\"1234567\".matches(pin) + \" \" + \"12 34\".matches(pin));\n        String name = \"[A-Z][a-z]*\";\n        System.out.println(\"Bo\".matches(name) + \" \" + \"B\".matches(name) + \" \" + \"bo\".matches(name));\n        System.out.println(\"Ann-Marie\".matches(name + \"(-\" + name + \")?\"));\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        String pin = \"[0-9]{4,6}\";\n        System.out.println(\"0815\".matches(pin) + \" \" + \"123\".matches(pin));\n        System.out.println(\"1234567\".matches(pin) + \" \" + \"12 34\".matches(pin));\n        String name = \"[A-Z][a-z]*\";\n        System.out.println(\"Bo\".matches(name) + \" \" + \"B\".matches(name) + \" \" + \"bo\".matches(name));\n        System.out.println(\"Ann-Marie\".matches(name + \"(-\" + name + \")?\"));\n    }\n}\n","hints":["`[0-9]{4,6}` is four to six digits, and nothing else: seven digits are too many, and a space isn't a digit.","`[A-Z][a-z]*` is one capital letter followed by any number of small letters, zero too. The last regex is `[A-Z][a-z]*(-[A-Z][a-z]*)?`: a name, and optionally a dash and a second name."],"tests":[{"name":"Output","stdin":"","expect":"true false\nfalse false\ntrue true false\ntrue","hidden":false}],"require":[],"forbid":[],"lines":["true false","false false","true true false","true"]},{"task":"In the Netherlands, a postcode is four digits, the first of which isn't 0, then a space and two capital letters, like `1012 AB`. A web shop accepts them with or without the space. Write the method `isPostcode(String text)`, which returns whether a string is a postcode of that form, with one call to `matches`. The check runs:\n\n```java\nSystem.out.println(isPostcode(\"1012 AB\"));\nSystem.out.println(isPostcode(\"1012AB\"));\nSystem.out.println(isPostcode(\"0123 AB\"));\nSystem.out.println(isPostcode(\"1012 ab\"));\n```\n\nand expects:\n\n```\ntrue\ntrue\nfalse\nfalse\n```\n","kind":"code","seed":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(isPostcode(\"1012 AB\"));\n        System.out.println(isPostcode(\"1012AB\"));\n        System.out.println(isPostcode(\"0123 AB\"));\n        System.out.println(isPostcode(\"1012 ab\"));\n    }\n\n    public static boolean isPostcode(String text) {\n        return false;\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(isPostcode(\"1012 AB\"));\n        System.out.println(isPostcode(\"1012AB\"));\n        System.out.println(isPostcode(\"0123 AB\"));\n        System.out.println(isPostcode(\"1012 ab\"));\n    }\n\n    public static boolean isPostcode(String text) {\n        return text.matches(\"[1-9][0-9]{3} ?[A-Z]{2}\");\n    }\n}\n","hints":["The first digit is a class of its own, `[1-9]`, and the three other digits are `[0-9]{3}`. The letters are `[A-Z]{2}`.","A `?` after the space makes the space optional: `[1-9][0-9]{3} ?[A-Z]{2}`."],"tests":[{"name":"Test 1","stdin":"","call":"System.out.println(isPostcode(\"1012 AB\"));\nSystem.out.println(isPostcode(\"1012AB\"));\nSystem.out.println(isPostcode(\"0123 AB\"));\nSystem.out.println(isPostcode(\"1012 ab\"));\n","expect":"true\ntrue\nfalse\nfalse","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(isPostcode(\"9999 ZZ\") + \" \" + isPostcode(\"1000AA\") + \" \" + isPostcode(\"1012  AB\") + \" \" + isPostcode(\"101 AB\"));\nSystem.out.println(isPostcode(\"10123 AB\") + \" \" + isPostcode(\"1012 ABC\") + \" \" + isPostcode(\"1000 A\") + \" \" + isPostcode(\"\"));\n","expect":"true true false false\nfalse false false false","hidden":true},{"name":"System.out.println(isPostcode(\"5611 EM\") + \" \" + isPostcode(\"5611-EM\") + \" \" + isPostcode(\"56I1 EM\") + \" \" + isPostcode(\" 5611 EM\"));","stdin":"","call":"System.out.println(isPostcode(\"5611 EM\") + \" \" + isPostcode(\"5611-EM\") + \" \" + isPostcode(\"56I1 EM\") + \" \" + isPostcode(\" 5611 EM\"));\n","expect":"true false false false","hidden":true}],"require":[{"pattern":"\\.matches\\s*\\(","min":1,"max":1,"message":"Check the postcode with one call to `matches`, whose regex allows the space to be there or not."}],"forbid":[]}]}