6
\$\begingroup\$

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:

  1. Usernames can only use alpha-numeric characters.
  2. 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.
  3. Username letters can be lowercase and uppercase.
  4. 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.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Do "letters" mean only English ones (ASCII [a-z]), or Unicode letters? For usernames you may want the latter. \$\endgroup\$
    – Trang Oul
    Commented May 16, 2023 at 8:38
  • \$\begingroup\$ Just a quick note that perhaps the digit requirement could be written a tad more tersely with (?:\d+)?$ (a non-capturing group matching zero or one time) \$\endgroup\$
    – morbusg
    Commented May 16, 2023 at 16:14
  • \$\begingroup\$ @TrangOul For this it's only English letters. \$\endgroup\$ Commented May 16, 2023 at 18:42

1 Answer 1

5
\$\begingroup\$

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");
}

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Instead of [a-z][a-z]+, just do [a-z]{2,}. It's the same, but more concise and readable. \$\endgroup\$ Commented May 15, 2023 at 19:41
  • \$\begingroup\$ True. It doesn't work anyway, I missed an edge case. Back in a jiffy. \$\endgroup\$
    – ggorlen
    Commented May 15, 2023 at 19:42
  • 1
    \$\begingroup\$ OK, should be all fixed. \$\endgroup\$
    – ggorlen
    Commented 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\$ Commented 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\$ Commented May 15, 2023 at 20:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.