Question : [密碼欄位檢查] 內容不可全數字或全英文, 範例輸出入如下 (input → output)。
> Input a string: 123
介紹到這, 有點不太甘願, 隨手版有沒有優化空間 ? 如果把 String.ToLower() 操作拿掉呢 ?
看到了嗎 ? XNORIsStrongPassword() 版本自我進化快 1 倍以上了, 就只是一行不起眼的 ToLower() 拿掉而已。是否效能跟可讀性這時候分家了呢 ? 是這樣的, 如果應用程式或服務沒有 1 秒鐘跑個上百萬次的需求, 建議選擇高可讀性, 也就是 LINQ 的版本, 然後是 Regex 的版本, 畢竟代碼只有一行, 讓後續接手的人可以關注在此, 自我解釋性也很不錯; 但有高頻處理的需求 , 就可以考慮邏輯閘的運算版本。需求不同, 實作就會有差異, 各取所需囉。
補充, Char 類別其實還有 IsLetterOrDigit() 方便的函式, 如果有需要可以直接取用。文末來欣賞 Char.IsDigit()、Char.IsLetter() 原始碼 內部有沒有什麼有趣的實作。
- 123213123123 → false (因為全數字)
- adfdsbrr → false (因為全英文)
- abc123 → true
public static bool ASCIIIsStrongPassword(string s)
{
s = s.ToLower();
long i;
if (long.TryParse(s, out i)) return false; //全數字NG
if (s.Any(c => c < 'a' || c > 'z')) return true; //擁有一個非英文就pass
return false;
}
public static bool XORIsStrongPassword(string s)
{
s = s.ToLower();
bool isDigit = false;
bool isLetter = false;
for (var i = 0; i < s.Length; ++i)
{
if (s[i] >= '0' && s[i] <= '9') isDigit = true;
else if (s[i] >= 'a' && s[i] <= 'z') isLetter = true;
if (isDigit && isLetter) return true;
}
return !isDigit && !isLetter;
}
這兩個算創意版本, 有點囉嗦而且還要把字串 ToLower() 。- 第一個版本針對數字做 TryParse(), 如果回傳 True 表示內容都是數字, 當然就是很弱的密碼; 若第一個判斷過了, 再追打內容是否都是英文, 用到 LINQ 以及 ASCII 值判斷, 相當容易混淆的實作。
- 第二個版本, 就更囉嗦了, 訪問每個字元, 打上是否為英文或數字的記號。若其中一個為 True, 另一個為 False, 那代表是清一色的英文或數字; 兩者皆有或皆沒有, 就是稍微 strong的 password。
public static bool RegexIsStrongPassword(string s)
{
return !Regex.IsMatch(s, @"^[0-9]+$") && !Regex.IsMatch(s, @"^[a-zA-Z]+$");
}
public static bool LINQIsStrongPassword(string s)
{
return !s.All(Char.IsDigit) && !s.All(Char.IsLetter);
}
是不是一兩行就搞定了 ! 當然 regex 是不符合命題的版本, 但看到簡潔的代碼還是會想筆記下來; 而另一個 LINQ 語法版本直接訪問字串中每個字元, 用 Char 類的 IsDigit()、IsLetter() 來操作, 非常精簡易讀。以上累計 4 個版本, 猜猜隨手版效能是否還不錯, 雖然囉嗦點 ? 跑個 100 萬次來試試。> Input a string: 123
> Input a string: abc
> Input a string: abc123
答案揭曉, LINQ + Char 操作版本是最快的, 而 Regex 版本慢非常多, 兩者差異有 10 倍以上。隨手版 XORIsStrongPassword() 速度還不錯, 但只能當玩具 (千萬別放到團隊代碼中, 會被殺的)。說實在, regex 可以考慮放在一些小型的應用程式或檢核上, 如果搭配 config 字串動態判斷 regex 的條件, 還是滿靈活的。介紹到這, 有點不太甘願, 隨手版有沒有優化空間 ? 如果把 String.ToLower() 操作拿掉呢 ?
public static bool XNORIsStrongPassword(string s)
{
bool isDigit = false;
bool isLetter = false;
for (var i = 0; i < s.Length; ++i)
{
if (s[i] >= '0' && s[i] <= '9') isDigit = true;
else if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z')) isLetter = true;
if (isDigit && isLetter) return true;
}
return !(isDigit ^ isLetter); // (1,0), (0,1) -> false; (0,0), (1,1) -> True
}
沒錯, 更醜陋了, 但大體還是看得懂吧 ? (不是屍體...), 趕緊回頭再跟第一名相互 PK, 一樣, 100 萬次。看到了嗎 ? XNORIsStrongPassword() 版本自我進化快 1 倍以上了, 就只是一行不起眼的 ToLower() 拿掉而已。是否效能跟可讀性這時候分家了呢 ? 是這樣的, 如果應用程式或服務沒有 1 秒鐘跑個上百萬次的需求, 建議選擇高可讀性, 也就是 LINQ 的版本, 然後是 Regex 的版本, 畢竟代碼只有一行, 讓後續接手的人可以關注在此, 自我解釋性也很不錯; 但有高頻處理的需求 , 就可以考慮邏輯閘的運算版本。需求不同, 實作就會有差異, 各取所需囉。
補充, Char 類別其實還有 IsLetterOrDigit() 方便的函式, 如果有需要可以直接取用。文末來欣賞 Char.IsDigit()、Char.IsLetter() 原始碼 內部有沒有什麼有趣的實作。
[Pure]
public static bool IsLetterOrDigit(char c)
{
if (IsLatin1(c))
{
return (CheckLetterOrDigit(GetLatin1UnicodeCategory(c)));
}
return (CheckLetterOrDigit(CharUnicodeInfo.GetUnicodeCategory(c)));
}
[Pure]
public static bool IsDigit(char c)
{
if (IsLatin1(c))
{
return (c >= '0' && c <= '9');
}
return (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber);
}
[Pure]
public static bool IsLetter(char c)
{
if (IsLatin1(c))
{
if (IsAscii(c))
{
c |= (char)0x20;
return ((c >= 'a' && c <= 'z'));
}
return (CheckLetter(GetLatin1UnicodeCategory(c)));
}
return (CheckLetter(CharUnicodeInfo.GetUnicodeCategory(c)));
}
也是有用到 ASCII 判斷哦。你還有更好的寫法嗎 ? 歡迎留言分享 ~參考資料
- https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/char.cs
- https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/string.cs
- https://stackoverflow.com/questions/1181419/verifying-that-a-string-contains-only-letters-in-c-sharp
Comments
Post a Comment