Алгоритм функционального программирования для поиска повторяющихся символов

Я преобразовываю алгоритм надежности пароля 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 может быть улучшено.

Другие вопросы по тегам