Vivek Vardhan
Vivek Vardhan

Reputation: 1178

Matching only one occurrence of a character from a given set

I need to validate an input string such that validation returns true only if the string contains one of the special characters @ # $ %, only one, and one time at the most. Letters and numbers can be anywhere and can be repeated any number of times, but at least one number or letter should be present

For example:

a@ : true

@a : true

a@$: false

a@n01 : true

an01 : false

a : false

@ : false

I tried

 [0-9A-Za-z]*[@#%$]{1}[0-9A-Za-z]*

I was hoping this would match one occurrence of any of the special characters. But, no. I need only one occurrence of any one in the set.

I also tried alternation but could not solve it.

Upvotes: 9

Views: 19432

Answers (2)

zx81
zx81

Reputation: 41838

Vivek, your regex was really close. Here is the one-line regex you are looking for.

^(?=.*?[0-9a-zA-Z])[0-9a-zA-Z]*[@#$%][0-9a-zA-Z]*$

See demo

How does it work?

  1. The ^ and $ anchors ensure that whatever we are matching is the whole string, avoiding partial matches with forbidden characters later.
  2. The (?=.*?[0-9a-zA-Z]) lookahead ensures that we have at least one number or letter.
  3. The [0-9a-zA-Z]*[@#$%][0-9a-zA-Z]* matches zero or more letters or digits, followed by exactly one character that is either a @, #, $ or %, followed by zero or more letters or digits—ensuring that we have one special character but no more.

Implementation

I am sure you know how to implement this in Java, but to test if the string match, you could use something like this:

boolean foundMatch = subjectString.matches("^(?=[0-9a-zA-Z]*[@#$%][0-9a-zA-Z]*$)[@#$%0-9a-zA-Z]*");

What was wrong with my regex?

Actually, your regex was nearly there. Here is what was missing.

  1. Because you didn't have the ^ and $ anchors, the regex was able to match a subset of the string, for instance a# in a##%%, which means that special characters could appear in the string, but outside of the match. Not what you want: we need to validate the whole string by anchoring it.
  2. You needed something to ensure that at least one letter or digit was present. You could definitely have done it with an alternation, but in this case a lookahead is more compact.

Alternative with Alternation

Since you tried alternations, for the record, here is one way to do it:

^(?:[0-9a-zA-Z]+[@#$%][0-9a-zA-Z]*|[0-9a-zA-Z]*[@#$%][0-9a-zA-Z]+)$

See demo.

Let me know if you have any questions.

Upvotes: 13

skiwi
skiwi

Reputation: 69269

I hope this answer will be useful for you, if not, it might be for future readers. I am going to make two assumptions here up front: 1) You do not need regex per se, you are programming in Java. 2) You have access to Java 8.

This could be done the following way:

private boolean stringMatchesChars(final String str, final List<Character> characters) {
    return (str.chars()
            .filter(ch -> characters.contains((char)ch))
            .count() == 1);
}

Here I am:

  1. Using as input a String and a List<Character> of the ones that are allowed.
  2. Obtaining an IntStream (consisting of chars) from the String.
  3. Filtering every char to only remain in the stream if they are in the List<Character>.
  4. Return true only if the count() == 1, that is of the characters in List<Character>, exactly one is present.

The code can be used as:

String str1 = "a";
String str2 = "a@";
String str3 = "a@@a";
String str4 = "a#@a";
List<Character> characters = Arrays.asList('@', '#', '$', '%');

System.out.println("stringMatchesChars(str1, characters) = " + stringMatchesChars(str1, characters));
System.out.println("stringMatchesChars(str2, characters) = " + stringMatchesChars(str2, characters));
System.out.println("stringMatchesChars(str3, characters) = " + stringMatchesChars(str3, characters));
System.out.println("stringMatchesChars(str4, characters) = " + stringMatchesChars(str4, characters));

Resulting in false, true, false, false.

Upvotes: 2

Related Questions