我在Python中实现了一个函数,用于检查密码是否足够强大。如果密码通过5次检查中的3次,则密码足够强。这是Python函数:
def is_valid(password):
checks = {
lambda x : True : 10,
lambda x : x.isupper() : 2,
lambda x : x.islower() : 2,
lambda x : x.isdigit() : 2,
lambda x : x in frozenset("~!@#$%^&*()-=_+[]{}<>?/\\`") : 2,
}
for c in password:
for func in list(checks):
if func(c):
checks[func] -= 1
if checks[func] == 0:
del checks[func]
if len(checks) <= 2:
return True
return False
如果足够强大,此功能可以在无限密码上运行。如果不是,则该功能将挂起:
>>> is_valid(itertools.cycle("!!xxxxxxxx"))
True
>>> is_valid(itertools.cycle("UUxxxxxxxx"))
True
我想知道如果我能以更优雅的方式在Haskell中实现相同的功能,那么我想出了这个解决方案:
isValid :: String -> Bool
isValid password =
let
checks = atLeast 10 password:map containsChars [isUpper, isLower, isSpecialChar, isDigit]
in atLeast 3 $ filter (==True) checks
where
containsChars predicate = length (take 2 $ filter predicate password) == 2
isSpecialChar c = isPunctuation c || isSymbol c
atLeast n seq = length (take n seq) == n
这个解决方案似乎更优雅,但与程序解决方案相比有一个缺点。如果密码是无限的并且没有足够的大写字符,则即使该函数通过其他条件,该函数也会挂起:
*Main Data.Char> isValid (cycle "UUxxxxxxxx")
True
*Main Data.Char> isValid (cycle "!!xxxxxxxx")
-- hangs
有没有办法在Haskell中实现一个没有这个缺点的优雅灵魂?
BTW:我可以使用Haskell内置的内容而不是我实现的atLeast
函数吗?
答案 0 :(得分:4)
你是对的:如果已知密码正常,它仍会尝试建立其他条件。因此我会采用不同的方式:
check passwd = go criteria passwd
where
criteria = [(10, const True), (2, isUpper), (2, isLower), (2, isDigit), (2, isSpecial)]
-- password is ok if at least 3 criteria have counted down to 0
ok = (>=3) . length . filter (==0) . map fst
go crit pwd
| ok crit = True
| null pwd = False
| otherwise = go (map (trans (head pwd)) crit) (tail pwd)
trans ch (0, p) = (0, p)
trans ch (n, p) = if p c then (n-1, p) else (n, p)
如果条件列表指示passwd正常并且在这种情况下返回True,则想法检查每个字符。然后检查是否已到达密码的结尾,在这种情况下,passwd无效。否则,我们还没有建立所有需要的标准,但是有一个非空的密码。因此,我们通过将计数器不为0的所有函数应用于当前字符,向下计算成功并递归来转换条件列表。
请注意(10, const True)
对条件进行编码:“Passwd长度至少为10个字符。”
答案 1 :(得分:1)
来自atLeast
的{{1}}函数的inBounds
函数稍微高效一点:
Data.Edison.Seq.ListSeq
答案 2 :(得分:1)
检查一下。我们的想法是拥有无限的成功检查清单。 import Data.Char
isValid password = let
checks = [isUpper, isLower, isSpecialChar, isDigit]
isSpecialChar c = isPunctuation c || isSymbol c
toInt b = if b then 1 else 0
check c = map toInt $ map ($c) checks
tests = zip [1..] $ map check password
accum passed ((len,t):ts) = (len,result):accum result ts where result = zipWith (+) passed t
accum _ [] = []
valid (len,test) = toInt ( len > 10 ) + (length $ filter (>=2) test ) >= 3
in any valid $ accum (repeat 0) tests
答案 3 :(得分:0)
只是为了好玩,我想我会把一个非常不同的答案扔给其他人。我的想法是定义一个跟踪密码统计信息的monoid,以及对密码有效的monoid进行单调检查。首先,一些预赛:
{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving #-}
import Data.Char
import Data.Monoid
import Data.Universe
deriving instance Num a => Num (Sum a) -- this should really be in base
我们跟踪的统计数据是我们看到的与您的五个条件相匹配的字符数。从技术上讲,我们实际上可以在数据结构中跟踪这一点,将函数映射到数字,这是最纯粹的FP方式,但如果天真地完成,它的效率会有点低。如果我们有一个明确的数据结构作为我们地图中的关键字,那么跟踪起来要容易得多。
data Bucket = Char | Upper | Lower | Digit | Punctuation
deriving (Eq, Ord, Show, Read, Bounded, Enum)
instance Universe Bucket
type Natural = Integer -- lol, let's just pretend, okay?
type Statistic = Bucket -> Sum Natural
Monoid
的{{1}}实例会在每个存储桶中添加计数。现在,要将Statistic
数据类型连接到更纯净的FP世界,我们将有一个解释器来自我们的&#34; EDSL&#34;进入Haskell函数。
Bucket
使用此解释器,我们可以将单个字符转换为统计类型的元素。这个来源可能看起来有些令人生畏,但实际上它只不过是一堆串联的转换功能。
interpret :: Bucket -> (Char -> Bool)
interpret Char = const True
interpret Upper = isUpper
interpret Lower = isLower
interpret Digit = isDigit
interpret Punctuation = isPunctuation
到目前为止,这些代码中没有一个实际上有太多&#34;业务逻辑&#34;在其中 - 也就是说,我们想要拥有的每个桶中确切的字符数或者我们想要填充多少个桶。所以,让我们写下这两件事。前者我们只是写一个统计数据。检查我们是否填充了足够的桶是有点棘手的,所以它值得一些解释。我们的想法是遍历所有存储桶,如果存储桶已经满了就会留下令牌。通过将其与阈值进行比较。如果我们获得足够的令牌,则统计数据有效。
statistic :: Char -> Statistic
statistic c b = fromIntegral . fromEnum . interpret b $ c
thresholds :: Statistic
thresholds Char = 10
thresholds _ = 2
validStatistic :: Statistic -> Bool
validStatistic f = length [() | b <- universe, f b >= thresholds b] >= 3
持有的关键属性未在类型系统中反映出来。有很多不同的方式陈述这个属性;一种方法是观察validStatistic
无论validStatistic f < validStatistic (f <> g)
和f
是什么(仍然假装没有负数)。在英语中,这对我们来说意味着如果密码的某些子字符串有效,那么整个密码也是有效的。
冷却。现在我们有了检查密码是否有效所需的所有部分。我们要做的是将密码的每个字符变成统计信息;我们所有的统计数据集合在一起;并检查统计数据是否曾经从无效到有效超过阈值(注意,如果确实如此,由于上面的关键属性,它将永远不会交叉回无效)。
g
在ghci中尝试:
validPassword :: String -> Bool
validPassword = any validStatistic . scanl (<>) mempty . map statistic
P.S。您是否注意到*Main> validPassword (cycle "UUxxxxxxx")
True
*Main> validPassword (cycle "!!xxxxxxx")
True
*Main> validPassword "!!xxxxxxx"
False
*Main> validPassword "!!xxxxxxxx"
True
基本上以与map-reduce相同的方式实现? (validPassword
是地图部分; map statistic
可以是scanl (<>) mempty
而不是缩小部分; foldr (<>) mempty
与any validStatistic
一起使用时会变为validStatistic
并且是后处理部分)如果您确实拥有100MB密码,可以很容易地看到如何并行化foldr
的计算。 ; - )
答案 4 :(得分:0)
你可以使用Monoids或Monad:
data Pass a = NotPass | TryPass Int | Pass a deriving Show
instance Monad Pass where
return = Pass
fail = NotPass
NotPass >>= _ = NotPass
Pass k >>= f = f k
(TryPass a k) >>= f = case f k of
NotPass -> if (a < 0) NotPass else TryPass (a-1)
TryPass b -> TryPass b
Pass _ -> TryPass a
is_valid :: String -> Bool
is_valid password = toBool $ do
TryPass 3
toPass isUpper
toPass isLower
toPass isSpecialChar
toPass isDigit
where
toPass bf = unless (bf bassword) NotPass
toBool NotPass = false
toBool _ = true