Просто для удовольствия, я думал, что сталкивать очень разные ответы на другие. Идея, которую я имел, - определить моноид, который отслеживает статистику о пароле, и монотонную проверку того моноида, что пароль действителен. Во-первых, некоторые отборочные:
{-# 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
экземпляр для Statistic
добавляет счетчики в каждом ведре. Теперь, чтобы подключить тип данных 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
До здесь, ни один из кода на самом деле не было много «бизнес-логики» в нем - то есть, именно то, сколько символов в каждом ведре мы хотим иметь или сколько ведер мы хотим заполнить. Итак, давайте запишем эти две вещи. Первое мы просто напишем как статистику. Проверка того, что мы заполняем достаточно ведер, немного сложна, поэтому она заслуживает некоторого объяснения. Идея состоит в том, чтобы перебирать все ведра и оставлять токен, если ведро «заполнено», сравнивая его с порогами. Если мы получим достаточно токенов, статистика действительна.
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
(все еще притворяются, что нет отрицательных чисел). На английском языке это будет означать для нас, что если какая-либо подстрока пароля действительна, то и весь пароль также действителен.
Прохладный. Теперь у нас есть все части, которые нам нужны, чтобы проверить, действительно ли пароль. Что мы сделаем, так это превратить каждый символ пароля в статистику; конгломерат всех статистических данных, когда мы идем; и проверяет, когда статистика когда-либо пересекает пороговое значение от недействительного до действительного (отмечая, что если он это сделает, он никогда не перейдет обратно к недопустимому из-за критического свойства выше).
validPassword :: String -> Bool
validPassword = any validStatistic . scanl (<>) mempty . map statistic
Попробуйте в GHCI:
*Main> validPassword (cycle "UUxxxxxxx")
True
*Main> validPassword (cycle "!!xxxxxxx")
True
*Main> validPassword "!!xxxxxxx"
False
*Main> validPassword "!!xxxxxxxx"
True
P.S. Вы заметили, что validPassword
в основном реализованы так же, как и сокращение карты? (map statistic
это карта часть; scanl (<>) mempty
может быть foldr (<>) mempty
вместо это уменьшить часть, any validStatistic
станет validStatistic
при использовании с foldr
и является частью пост-обработки) делает его легко увидеть, как распараллелить вычисления validPassword
в случае, если действительно есть пароли 100 МБ. ;-)
мне очень интересно; «бесконечный пароль» - термин, о котором я просто не знаю? –
@AndrewBarber - Это не то, что можно использовать в практике. Это просто предположение о «худшем случае». Вы также можете ввести пароль в размере 100 МБ. Процедурный алгоритм не будет перебирать все 100 МБ, если пароль достаточно сильный, а алгоритм в Haskell может. – reish
Ahhh, ок. Я понимаю, что вы имеете в виду! –