2

Given a string, it must have the following:

  1. only one number
  2. only one special character in (@#$*)
  3. 6 lower cased letters

The string can be in any combination of the above criteria and must be 8 in length.

Examples:

  1. 2@qwerty
  2. 1asddfg

  3. qwe*yt2u
  4. qw2wqia

Here my regex so far:

!/^(?=.*[0-9])(?=.*[^a-z])(?=.*[a-z])(?=.*[@#$*])\S{8,}$/.test(string)

It works for the above cases but breaks on cases like below:

  1. 2@2qwert
  2. 2@@qwert

What am I missing?

7
  • 1
    Why don't just break it into 4 simple regular expressions and require them all to match? At least will be easier to implement and read. Commented Aug 27, 2013 at 20:40
  • Why use regex?? Doesn't make any sense. Commented Aug 27, 2013 at 20:41
  • "breaks on cases like below": should they pass or fail? Fail, I assume. There is nothing requiring 6 letters in your pattern, so why should they be rejected? Also, your first and fourth lookahead make sure that the second will always pass, so it's redundant. Commented Aug 27, 2013 at 20:41
  • Not sure what's missing. If you've not tried it, I recommend Expresso by Ultrapico (free to download and register) Commented Aug 27, 2013 at 20:42
  • How is it breaking btw? TBH this looks like it shouldnt even work for your other groups. Commented Aug 27, 2013 at 20:47

5 Answers 5

3

What am I missing?

The reasons your tests '2@2qwert' and '2@@qwert' are incorrectly matching your regex is because there isn't anything in it that requires a minimum of 6 lowercase letters. Based on the other answers and comments to this question, I'd say replace your (?=.*[a-z]) clause with (?=(.*?[a-z]){6}).

Some other minor improvements that can be made:

  • You can drop the redundant (?=.*[^a-z]) clause since all this is saying is that the string should contain at least 1 non-letter, which is already established by the digit and special character requirement.
  • Replace [0-9] with \d.
  • In the 3 places where you are matching wildcards prior to a character match (.*), it is slightly faster for the RegExp engine if these are made non-greedy so that there is less backtracking done when searching through the string for a match. This is done by placing a ? after the * (.*?).

Putting this together based on your regexp:

/^(?=.*?\d)(?=(.*?[a-z]){6})(?=.*?[@#$*])\S{8,}$/

This successfully matches your first 4 strings, but not the last 2.

(My original response is below in case you want a readable validate function.)

function validate(str)
{
  // test for digit
  if( !/\d/.test(str) ) return false;
  // test for special character
  if( !/[@#$*]/.test(str) ) return false;
  // test for 6 lowercase letters
  var letters = str.match(/[a-z]/g);
  return letters != null && letters.length == 6;
}

var tests = [ '2@qwerty', '#1asddfg', 'qwe*yt2u', '#qw2wqia', '2@2qwert', '2@@qwert' ];
for( var i=0 ; i<tests.length ; ++i )
  document.writeln(tests[i] + ": " + validate(tests[i]) + "<br/>\n");
Sign up to request clarification or add additional context in comments.

Comments

3

Your breaking cases should break because they do not satisfy condition #3. Having said that, I think its probably easier to use a combination of JavaScript and Regex:

function isValid(input) {
  return (input && input.length === 8)             /* make sure its 8 characters */
         && /[0-9]/.test(input)                    /* make sure it contains at least one digit */
         && /[@#$*]/.test(input)                   /* make sure it contains at least one special character */
         && /([^a-z]*[a-z]){6}.*/.test(input);     /* make sure it contains at least 6 lower case chars */ 
}

console.log(isValid('2@qwerty'));    // true
console.log(isValid('#1asddfg'));    // true
console.log(isValid('qwe*yt2u'));    // true
console.log(isValid('#qw2wqia'));    // true
console.log(isValid('2@2qwert'));    // false
console.log(isValid('2@@qwert'));    // false
console.log(isValid('2@qwerty'));   // now true as it satisfies #3

Edit: Updated the check for minimum of 6 chars based on inputs from @Sniffer (it was interesting to learn something new, see comments!)

7 Comments

+1 Nice regex /([^a-z]?[a-z]){6}.*/ to test for 6 non-consecutive letters. I like your solution better than mine.
@Matt I don't think /([^a-z]?[a-z]){6}.*/ works, try af34ddfg It won't match
@Matt You are correct, on the line of your observation I was able to improve my expression a bit.
/([^a-z]?[a-z]){6}.*/ works in this case as the overall string is limited to 8 characters (6 letters, 1 number, and 1 symbol) so the string af34ddfg would fail because it has 2 numbers and zero symbols which are tested at other parts of this script.
@Sniffer: Strangely, my regex works for 'qwe*yt2u' but not for 'af34ddfg'; both of them have 6 non-consecutive characters.
|
2

Description

Your expression breaks because:

^(?=.*[0-9])(?=.*[^a-z])(?=.*[a-z])(?=.*[@#$*])\S{8,}$
            ^^^^^^^^^^^^                       ^^^^^^
  • [^a-z] is a negated character class, so this will match anything which is not a-z. I'm not sure what the purpose of this portion of the expression was for.
  • \S will match any non white space character. This is allows for letters, numbers, unicode characters, new line characters, any symbols... etc.
  • {8,} will allow 8 or more of the preceiding characters. This allows a string to be an infinite length. In your post you wanted the string to be at most 8 characters.
  • also as a best practice you should always escape the # as this can be used as a comment character in some versions of regex so the expression is ready in case the x option is ever used.

I would modify your expression like this:

  • require the string to have 1 number
  • require the string to have 1 @#$* symbol
  • require the string to have 6 a-z lower case letters (be sure not to use the case insenstive option)
  • require the over all length of the string to have 8 characters

^(?=.*?[0-9])(?=.*?[@\#$*])(?=(?:.*?[a-z]){6}).{8}$

Live Demo

enter image description here

NODE                     EXPLANATION
--------------------------------------------------------------------------------
  ^                        the beginning of the string
--------------------------------------------------------------------------------
  (?=                      look ahead to see if there is:
--------------------------------------------------------------------------------
    .*?                      any character except \n (0 or more times
                             (matching the least amount possible))
--------------------------------------------------------------------------------
    [0-9]                    any character of: '0' to '9'
--------------------------------------------------------------------------------
  )                        end of look-ahead
--------------------------------------------------------------------------------
  (?=                      look ahead to see if there is:
--------------------------------------------------------------------------------
    .*?                      any character except \n (0 or more times
                             (matching the least amount possible))
--------------------------------------------------------------------------------
    [@\#$*]                  any character of: '@', '\#', '$', '*'
--------------------------------------------------------------------------------
  )                        end of look-ahead
--------------------------------------------------------------------------------
  (?=                      look ahead to see if there is:
--------------------------------------------------------------------------------
    (?:                      group, but do not capture (6 times):
--------------------------------------------------------------------------------
      .*?                      any character except \n (0 or more
                               times (matching the least amount
                               possible))
--------------------------------------------------------------------------------
      [a-z]                    any character of: 'a' to 'z'
--------------------------------------------------------------------------------
    ){6}                     end of grouping
--------------------------------------------------------------------------------
  )                        end of look-ahead
--------------------------------------------------------------------------------
  .{8}                     any character except \n (8 times)
--------------------------------------------------------------------------------
  $                        before an optional \n, and the end of the
                           string

3 Comments

Very nicely formatted answer. My regex fu is not very strong so I was hoping someone could produce one, just for curiosity. I tried your live demo but it seems to fail where you don't have 6 consecutive lowercase chars: 1as1#ddfg@.
wow that an answer! @Mrchief your example would fail since it is already over 8 characters and also broken rules 1 and 2?!
@yaojiang: Yeah... For some reason, I was hung up on 6 lower chars part while ignoring the other conditions when I added that comment. My bad!
2

Well I came up with a regular expression that might do what you want but you may not like the look of it but the principle behind it is simple:

^(?=^.{8}$)(?=\D*[0-9])(?=[^@#$*]*[@#$*])(?=([^a-z]*[a-z]){6})\S{8}$

This expression first makes sure there is only 8 characters (?=^.{8}$).

Now the expression checks if there is a single number (?=\D*[0-9]) no more, then it makes sure there is one special symbol (?=[^@#$*]*[@#$*]).

Now the expression guarantees that there is 6 lower case characters (?=([^a-z]*[a-z]){6}).

So we have guaranteed there is one number, one special symbol and 6 lower case characters, the sum is 8 characters so the string should be valid.

This is not the best way to do it of course as you should probably break this operation into multiple steps but I wanted to try and do it with a single expression for the fun of it, if you found any problems please let me know.

Comments

2

This regex does what you need:

^(?=\D*\d\D*$)(?=[^@#$*]*[@#$*][^@#$*]*$)[@#$*\da-z]{8}$

It uses 2 look-aheads (anchored to start) to assert exactly 1 digit and 1 special, and a simple character class for all valid chars (times 8) so the rest must be lowercase (no need to count exactly 6).

See a live demo of your test cases on rubular.

3 Comments

Another interesting solution! However it fails on this one: asd1234ghy@
@Mrchief Fails? asd1234ghy@ shouldn't match: The question says only one number and must have exactly 8 characters - this example fails for both those reasons. I've added your example to this live demo. Let me know what you mean.
You're right. I was just focussing on the 6 lower chars part.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.