实际上,我有一个小问题是我正在开发应用程序,其中注册用户的表单和验证结束密码不能包含用户名,并且不能包含连续的 2 个字母形式的用户名。
假设用户名 id 为“Aspnetmvc”,那么密码不应包含整个单词“Aspnetmvc”甚至用户名的一部分,如 asp、net、mvc。这可以通过自定义逻辑来解决,但我想做的是通过编程逻辑来解决,但我想做的是找到用正则表达式解决这个问题的方法。
有人知道这个 C# 吗?
一个非常简单的解决方案是创建一个方法,该方法将从用户名和实名中提取所有可能的 3 个字母组合,并检查这些是否是密码的一部分。3 个字符的每个可能部分(超过 2 个)的方法可以编写为简单的扩展方法,然后您可以使用该IEnumerable.Any
方法查看这些部分是否是密码的一部分:
using System;
using System.Linq;
using System.Collections.Generic;
namespace ConsoleApplication5
{
static class Program
{
static void Main(string[] args)
{
string password = "1234567890";
string username = "125689";
string realName = "890";
bool usernameOk = !username.AllPartsOfLength(3)
.Any(part => password.Contains(part));
bool realNameOk = !realName.AllPartsOfLength(3)
.Any(part => password.Contains(part));
}
public static IEnumerable<string> AllPartsOfLength(this string value, int length)
{
for (int startPos = 0; startPos <= value.Length - length; startPos++)
{
yield return value.Substring(startPos, length);
}
yield break;
}
}
}
我发现这比任何包含正则表达式的解决方案更容易阅读。
你甚至可以这样做:
passwordOk = !username.AllPartsofLength(3)
.Concat(realName.AllPartsOfLength(3))
.Any(part => password.Contains(part));
由于这些使用惰性评估,因此在找到第一部分时评估将停止。
确实没有必要,也没有充分的理由尝试使用正则表达式来执行此操作。您可以使用的唯一表达式是检查字符串中是否存在任何 3 个字母部分的表达式。所以你仍然必须将字符串分成 3 部分,然后构建一个表达式,让运行时为此构建一个状态机,根据输入检查它,然后丢弃表达式。对于手头的问题,这是昂贵的。
它看起来像这样:
IEnumerable<string> parts = username.AllPartsOfLength(3)
.Concat(realName.AllPartsOfLength(3))
.Select(part => Regex.Escape(part));
string regex = "(" + string.Join("|", parts) + ")";
bool isPasswordOk = !Regex.Match(regex).Success;
添加基准
根据 sln 的要求,一个简短的基准:
方法:StringManipulationOnly 所用时间:26,0015 毫秒。通过:3333。失败 6666。
方法:RegexStringJoinAllParts 耗时:486,0278ms。通过:3333。失败 6666。
方法:RegexZeroWidthPlusOneAndDotSplat 耗时:5686,3252ms。通过:3333。失败 6666。
方法:RegexZeroWidth 耗时:2659,1521ms。通过:3333。失败 6666。
编辑 在删除 e.* 的情况下进行了另一次测试,但额外的 . 保存在那里
方法:RegexZeroWidthPlusOne 耗时:2601,1488ms。通过:3333。失败 6666。
正如您所看到的那样,要么.*
导致另一个 50% 的延迟,而且所有使用正则表达式拆分字符串的解决方案都比使用 string.Join 创建一个大表达式慢得多。到目前为止,明显的赢家不是使用正则表达式。
.*constant
对慢于的事实的解释constant
可能是由于 .* 将首先获取整个输入,然后开始回溯(从字符串的末尾)以查找常量,而constant
只会查找第一个的发生constant
。
一个简单的测试似乎证实了这一点(使用.*?
代替.*
):
方法:RegexZeroWidthPlusOneDotSplatReluctant 所用时间:2646,1514ms。通过:3333。失败 6666。
我确实对代码做了一些更改,我删除了区分大小写检查(OP 没有要求)我删除了参数验证,我将代码更改为提前失败。这确保了不同方法之间的公平比较。代码可以在这里找到。
您应该让正则表达式为您完成工作(?=(..)).
重做 4-29
static class Program
{
static void Main(string[] args)
{
string Password = "(O*@aJY^+{PC";
string Account = "email@Abc.com";
string Name = "Ted Nelson";
if (Password.IsNotSequentialChars(Account, 2) && Password.IsNotSequentialChars(Name, 2))
Console.WriteLine("Passed");
else
Console.WriteLine("Failed");
}
public static bool IsNotSequentialChars(this string Src, string Dest, int check_len)
{
if (check_len < 1 || Src.Length < check_len) return true;
Match m = Regex.Match(Src, "(?=(.{" + check_len + "})).");
bool bOK = m.Success;
while (bOK && m.Success)
{
// Edit: remove unnecessary '.*' from regex.
// And btw, is regex needed at all in this case?
bOK = !Regex.Match(Dest, "(?i)" + Regex.Escape(m.Groups[1].Value)).Success;
if (!bOK)
Console.WriteLine("Destination contains " + check_len + " sequential source letter(s) '" + m.Groups[1].Value + "'");
m = m.NextMatch();
}
return bOK;
}
}
欢迎提供基准...