Working through the freeCodeCamp JavaScript curriculum and I found this RegExp task surprisingly tricky (maybe I just don't use RegExp very much).
The parameters are:
- Usernames can only use alpha-numeric characters.
- The only numbers in the username have to be at the end. There can be zero or more of them at the end. Username cannot start with the number.
- Username letters can be lowercase and uppercase.
- Usernames have to be at least two characters long. A two-character username can only use alphabet letters as characters.
My logic is that since usernames must start with a letter we can begin with /^[a-zA-Z]/i
.
From there, a username can contain either:
1 or more letters, followed by 0 or more numbers at the end of the string
OR
2 or more numbers at the end of the string.
Which lead me to the following RegExp: /^[a-z]([a-z]+\d*$|\d{2,}$)/i
. This RegExp works but I don't write that much RegExp so I'm not confident it's as simple or elegant as it could be.
1 Answer 1
Your regex looks pretty close to optimal to me, unless I'm missing something. The only adjustments I'd make are:
- Write some tests if you haven't already (I did ad-hoc tests that can run in the browser, but preferably use a real library like Jest or Mocha).
- Use
?:
at the start of a parentheses group that you don't need to capture. This communicates the intent of the grouping more clearly to the reader. - Move
$
out of the parens to the end to avoid duplication and improve readability.
const p = /^[a-z](?:[a-z]+\d*|\d{2,})$/i;
const shouldFail = [
"a",
"A2",
"2",
"2a",
"a2a",
"z23a",
"2B2",
"AA45678a",
"",
];
const shouldPass = [
"aa",
"aa2",
"a22",
"AA23",
"aa233",
"zA",
"AAA",
"AA45678",
];
const failures = [];
for (const t of shouldFail) {
if (p.test(t)) {
failures.push(`'${t}' is invalid but was reported as valid`);
}
}
for (const t of shouldPass) {
if (!p.test(t)) {
failures.push(`'${t}' is valid but was reported as invalid`);
}
}
failures.forEach(e => console.error(e));
if (failures.length === 0) {
console.log("All tests passed");
}
-
1\$\begingroup\$ Instead of
[a-z][a-z]+
, just do[a-z]{2,}
. It's the same, but more concise and readable. \$\endgroup\$Ismael Miguel– Ismael Miguel2023年05月15日 19:41:58 +00:00Commented May 15, 2023 at 19:41 -
\$\begingroup\$ True. It doesn't work anyway, I missed an edge case. Back in a jiffy. \$\endgroup\$ggorlen– ggorlen2023年05月15日 19:42:38 +00:00Commented May 15, 2023 at 19:42
-
1\$\begingroup\$ OK, should be all fixed. \$\endgroup\$ggorlen– ggorlen2023年05月15日 19:52:15 +00:00Commented May 15, 2023 at 19:52
-
\$\begingroup\$ Well, you said everything I would have said about the regex. However, I would add why you use
?:
in a group. (To avoid capturing it.) \$\endgroup\$Ismael Miguel– Ismael Miguel2023年05月15日 20:11:06 +00:00Commented May 15, 2023 at 20:11 -
1\$\begingroup\$ I know that. You know that. But does O.P. know that? Does a new person know that? While you say what it does, in the answer, you don't explain why you decided to use it. That's the only thing I would change to make your answer as perfect as it can be. \$\endgroup\$Ismael Miguel– Ismael Miguel2023年05月15日 20:43:17 +00:00Commented May 15, 2023 at 20:43
[a-z]
), or Unicode letters? For usernames you may want the latter. \$\endgroup\$(?:\d+)?$
(a non-capturing group matching zero or one time) \$\endgroup\$