Алгоритм функционального программирования для поиска повторяющихся символов
Я преобразовываю алгоритм надежности пароля zxcvbn из JavaScript в Scala. Я ищу чисто функциональный алгоритм для поиска последовательностей повторяющихся символов в строке.
Я знаю, что могу перевести императивную версию из JavaScript, но я бы хотел, чтобы это было как можно меньше побочных эффектов, по всем причинам, обычно указанным для функционального программирования.
Алгоритм может быть в Scala, Clojure, Haskell, F# или даже псевдокоде.
Благодарю.
4 ответа
Используя стандартные функции высшего порядка Haskell:
Data.List.group
находит серии одинаковых элементов в списке:> group "abccccdefffg" ["a","b","cccc","d","e","fff","g"]
Мы заботимся о длине этих трасс, а не о самих элементах:
> let ls = map length $ group "abccccdefffg" > ls [1,1,4,1,1,3,1]
Далее нам нужны стартовые позиции каждой группы. Это только частичные суммы длин групп, которые мы можем вычислить, используя
scanl
:> scanl (+) 0 ls [0,1,2,6,7,8,11,12]
Сжатие этих двух списков дает нам все пары начальных позиций и соответствующие длины:
> zip (scanl (+) 0 ls) ls [(0,1),(1,1),(2,4),(6,1),(7,1),(8,3),(11,1)]
Наконец, мы удаляем группы длиной менее 3.
> filter ((>= 3) . snd) $ zip (scanl (+) 0 ls) ls [(2,4),(8,3)]
Собираем это вместе:
import Data.List (group)
findRuns :: Eq a => [a] -> [(Int, Int)]
findRuns xs = filter ((>= 3) . snd) $ zip (scanl (+) 0 ls) ls
where ls = map length $ group xs
Вот еще одно решение Scala
def findRuns(password: String): List[(Int, Int)] = {
val zipped = password.zipWithIndex
val grouped = zipped groupBy { case (c, i) => c }
val filtered = grouped.toList.filter{ case (c, xs) => xs.length >= 3 }
val mapped = filtered map { case (c, xs) => xs }
val result = mapped map (xs => (xs.head._2, xs.length))
result.sorted
}
Clojure-версия алгоритма hammar
(defn find-runs [s]
(let [ls (map count (partition-by identity s))]
(filter #(>= (% 1) 3)
(map vector (reductions + 0 ls) ls))))
Вот решение Scala, которое я придумал:
def repeatMatch(password: String): List[(Int, Int)] = {
val length = password.length
@tailrec
def loop(offset: Int,
result: List[(Int, Int)]): List[(Int, Int)] = {
if (offset >= length) result.reverse
else {
val candidate = password.charAt(offset)
val run = password.substring(offset).zip(Array.fill(length)(candidate)).
takeWhile{case (first, second) => first == second}.length
if (run > 2) loop(offset + run, (offset, run) :: result)
else loop(offset + 1, result)
}
}
loop(0, List.empty[(Int, Int)])
}
Для теста repeatMatch("abccccdefffg")
результат List((2,4), (8,3))
Может быть, расчет run
может быть улучшено.