检查密码是否足够强-Haskell vs过程语言
我在Python中实现了一个函数,用于检查密码是否足够强。如果密码通过了5次检查中的3次,则密码足够强。这是Python函数:检查密码是否足够强-Haskell vs过程语言,haskell,functional-programming,Haskell,Functional Programming,我在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, l
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
我想知道我是否能够以更优雅的方式在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中实现一个优雅的解决方案而不存在这个缺点
顺便问一下:Haskell中是否有内置的功能,我至少可以使用它来代替我实现的
功能?你是对的:如果已知密码正常,它仍将尝试建立其他条件。因此,我会采取不同的做法:
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是ok的,则在本例中返回True。然后检查是否已到达密码的末尾,在这种情况下,passwd无效。否则,我们还没有建立所有需要的标准,但是有一个非空的passwd。因此,我们通过将计数器尚未为0的所有函数应用于当前字符、递减成功次数并递归来转换条件列表
请注意,(10,const True)
对条件进行编码:“Passwd至少有10个字符长。”您的函数至少有一个稍微高效的版本是Data.Edison.Seq.ListSeq
中的inBounds
函数:
inBounds i xs
| i >= 0 = not (null (drop i xs))
| otherwise = False
看看这个。这样做的目的是,到目前为止,拥有无数成功检查的列表。
导入数据.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
只是为了好玩,我想我会想出一个与其他答案完全不同的答案。我的想法是定义一个用于跟踪密码统计信息的幺半群,并对该幺半群进行单调检查以确定密码是否有效。首先,一些预备工作:
{-# 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
Statistic
的Monoid
实例添加每个bucket中的计数。现在,为了将Bucket
数据类型连接到更纯净的FP世界,我们将有一个解释器从“EDSL”转换到Haskell函数
interpret :: Bucket -> (Char -> Bool)
interpret Char = const True
interpret Upper = isUpper
interpret Lower = isLower
interpret Digit = isDigit
interpret Punctuation = isPunctuation
使用此解释器,我们可以将单个字符转换为统计类型的元素。其来源可能看起来有点吓人,但事实上它只不过是串在一起的一组转换函数
statistic :: Char -> Statistic
statistic c b = fromIntegral . fromEnum . interpret b $ c
到目前为止,所有代码中实际上都没有太多的“业务逻辑”——也就是说,每个bucket中我们想要有多少个字符,或者我们想要填充多少bucket。让我们写下这两件事。前者我们将作为一个统计数据来写。检查我们是否已经装满了足够的桶有点棘手,所以它值得一点解释。其思想是迭代所有bucket,如果bucket已“满”,则通过将其与阈值进行比较,留下一个标记。如果我们得到足够的代币,统计数据是有效的
thresholds :: Statistic
thresholds Char = 10
thresholds _ = 2
validStatistic :: Statistic -> Bool
validStatistic f = length [() | b <- universe, f b >= thresholds b] >= 3
在ghci中试用:
*Main> validPassword (cycle "UUxxxxxxx")
True
*Main> validPassword (cycle "!!xxxxxxx")
True
*Main> validPassword "!!xxxxxxx"
False
*Main> validPassword "!!xxxxxxxx"
True
另外,您是否注意到validPassword
基本上是以与map reduce相同的方式实现的?(map statistic
是map部分;scanl()mempty
可以是foldr()mempty
而是reduce部分;与foldr
一起使用时,任何validStatistic
都将成为validStatistic
,并且是后处理部分)如果您确实拥有100MB密码,则可以轻松查看如何并行计算validPassword
。-) 您可以使用幺半群或单子群:
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
data Pass a=NotPass | TryPass Int | Pass a派生显示
实例Monad Pass在哪里
返回=通过
失败=不通过
NotPass>>==NotPass
通过k>>=f=f k
(TryPass a k)>>=f=的情况f k
NotPass->if(a<0)NotPass else TryPass(a-1)
胰蛋白酶b->胰蛋白酶b
通行证a
_是否有效::String->Bool
_有效密码=toBool$do吗
胰蛋白酶3
托帕斯在上
托帕斯岛
托帕斯是一个特殊的人物
托帕斯数字
哪里
toPass bf=除非(bf bassword)未通过
toBool NotPass=错误
toBool=真
我很好奇;“无限密码”是我不知道的一个术语吗?@AndrewBarber-它不是可以在实践中使用的东西。这只是“更糟的情况”的假设。您也可以假定密码为100MB。如果密码足够强,过程算法将不会在整个100MB上迭代,而Haskell中的算法可能会。啊,好的。我明白你的意思!总是考虑什么…这是一个有趣的解决方案。我对此有一些评论:1。难道不能用foldl
代替accum吗?2.isValid“uxxx”由于函数accum中的非穷举模式而引发错误。由于您或
检查而不是检查它们,因此您的解决方案将“uxxx!”标记为强密码。但是,它应该至少包含到个特殊字符和至少两个大写字母才能成为强字符。@darwish更新的解决方案和问题的答案为“否”-fold\mapAccum将在此处强制进行完全计算。@darwishfoldl
在无限输入时始终无限递归。foldr
(比较两个函数的定义,试着写出两个递归步骤,看看它们在这方面有什么不同),但递归
*Main> validPassword (cycle "UUxxxxxxx")
True
*Main> validPassword (cycle "!!xxxxxxx")
True
*Main> validPassword "!!xxxxxxx"
False
*Main> validPassword "!!xxxxxxxx"
True
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