I'm trying to validate a password string in a .NET for sequential patterns (forward or reverse) with numbers or letters of 5 or more.
Examples of patterns that will not be accepted:
"ABCDE",
"12345",
"54321",
"edcba"
I cannot find a decent regex pattern that handles finding the characters in order, currently just returning any sequence of 5 letters or numbers:
public bool CheckForSequence(string input)
{
return Regex.IsMatch(input.ToUpper(), @"([A-Z])(?!\1)([A-Z])(?!\1|\2)([A-Z])(?!\1|\2|\3)([A-Z])(?!\1|\2|\3|\4)([A-Z])") ||
Regex.IsMatch(input, @"([1-9])(?!\1)([1-9])(?!\1|\2)([1-9])(?!\1|\2|\3)([1-9])(?!\1|\2|\3|\4)([1-9])");
}
CodePudding user response:
There are probably way better ways to do this, but, just for fun, I've made a simple brute-force algorithm:
bool CheckForSequence(string inp) {
bool InRange(int c) {
const int minLower = (int)'a';
const int maxLower = (int)'z';
const int minUpper = (int)'A';
const int maxUpper = (int)'Z';
const int minNumber = (int)'0';
const int maxNumber = (int)'9';
return (c >= minLower && c <= maxLower) || (c >= minUpper && c <= maxUpper) || (c >= minNumber && c <= maxNumber);
}
if(inp.Length < 5) return false;
for(var i = 0; i < inp.Length - 4; i )
{
var c = (int)inp[i];
if(InRange(c))
{
var vM = c;
int x;
for(x = i 1; x < i 5; x )
{
if(inp[x] != vM 1 || !InRange(inp[x])) break;
vM ;
}
if(x == i 5) return true;
for(x = i 1; x < i 5; x )
{
if(inp[x] != vM-1 || !InRange(inp[x])) break;
vM--;
}
if(x == i 5) return true;
}
}
return false;
}
You can see it in action in this fiddle
CodePudding user response:
Wiktor is correct - regex is the wrong tool for this.
Here's one possible implementation:
public static class SequenceChecker
{
private static char MapChar(char c) => c switch
{
>= '0' and <= '9' => c,
>= 'A' and <= 'Z' => c,
>= 'a' and <= 'z' => (char)(c - 'a' 'A'),
_ => default,
};
private static bool IsSequence(ReadOnlySpan<char> input)
{
char x = MapChar(input[0]);
if (x == default) return false;
char y = MapChar(input[1]);
if (y == default) return false;
int direction = y - x;
if (Math.Abs(direction) != 1) return false;
for (int index = 2; index < input.Length; index )
{
x = y;
y = MapChar(input[index]);
if (y == default) return false;
int nextDirection = y - x;
if (nextDirection != direction) return false;
}
return true;
}
public static bool ContainsSequence(string input, int sequenceLength = 5)
{
if (sequenceLength < 2) throw new ArgumentOutOfRangeException(nameof(sequenceLength));
if (input is null) return false;
if (input.Length < sequenceLength) return false;
for (int startIndex = 0; startIndex < 1 input.Length - sequenceLength; startIndex )
{
if (IsSequence(input.AsSpan(startIndex, sequenceLength)))
{
return true;
}
}
return false;
}
}
CodePudding user response:
Just to add to the plethora of solutions posted so far:
public static int LongestAscendingOrDescendingRun(string s)
{
if (s.Length <= 1)
return 0;
int longest = 0;
int current = 0;
bool ascending = false;
for (int i = 1; i < s.Length; i )
{
bool isAscending () => s[i]-s[i-1] == 1;
bool isDescending() => s[i]-s[i-1] == -1;
if (current > 0)
{
if (ascending)
{
if (isAscending())
{
longest = Math.Max(longest, current);
}
else // No longer ascending.
{
current = 0;
}
}
else // Descending.
{
if (isDescending())
{
longest = Math.Max(longest, current);
}
else // No longer descending.
{
current = 0;
}
}
}
else // No current.
{
if (isAscending())
{
ascending = true;
current = 2;
longest = Math.Max(longest, current);
}
else if (isDescending())
{
ascending = false;
current = 2;
longest = Math.Max(longest, current);
}
}
}
return longest;
}
CodePudding user response:
Like Wiktor has already said, regex isn't a good way to do this. You could find the difference between consecutive characters of the string, and complain if you find a sequence of four or more ones (or -1s).
public bool CheckForSequence(string pass)
{
int curr_diff = 0; // The difference between the i-1th and i-2th character
int consec_diff = 0; // The number of consecutive pairs having the same difference
for (int i = 1; i < pass.Length; i )
{
int diff = pass[i] - pass[i - 1]; // The difference between the ith and i-1th character
if (Math.Abs(diff) == 1 && curr_diff == diff)
{
// If the difference is the same, increment consec_diff
// And check if the password is invalid
consec_diff ;
if (consec_diff >= 4)
return false;
}
else
{
// New diff. reset curr_diff and consec_diff
curr_diff = diff;
consec_diff = Math.Abs(diff)==1 ? 1 : 0;
// If the difference is 1, set consec_diff to 1 else 0
}
}
return consec_diff < 4;
}