【问题标题】:functional programming algorithm for finding repeated characters查找重复字符的函数式编程算法
【发布时间】:2012-11-30 17:41:08
【问题描述】:

我正在将 "zxcvbn" password strength algorithm 从 JavaScript 转换为 Scala。我正在寻找一种纯函数算法来查找字符串中的重复字符序列。

我知道我可以从 JavaScript 翻译命令式版本,但出于函数式编程通常给出的所有原因,我希望尽可能避免产生副作用。

算法可以是 Scala、Clojure、Haskell、F#,甚至是伪代码。

谢谢。

【问题讨论】:

  • Data.List.group :: Eq a => [a] -> [[a]]
  • 该链接目前似乎不可用。您是否有机会在问题中更准确地指定您正在寻找的算法应该做什么?
  • JavaScript 算法正在寻找候选密码中所有重复字符的子字符串。它特别需要每次运行的从零开始的偏移量和长度。例如:“abccccdefffg”将返回 [(2,4),(8,3)],忽略长度小于 3 的运行。

标签: algorithm functional-programming repeat


【解决方案1】:

使用 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

【讨论】:

  • 比我的更优雅:-)。
【解决方案2】:

这是另一个 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
}

【讨论】:

    【解决方案3】:

    hammar 算法的 Clojure 版本

     (defn find-runs [s]
        (let [ls (map count (partition-by identity s))]
            (filter #(>= (% 1) 3) 
                    (map vector (reductions + 0 ls) ls))))
    

    【讨论】:

      【解决方案4】:

      这是我想出的 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的计算可以改进。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-12-09
        • 1970-01-01
        • 2014-08-05
        • 2017-04-16
        • 2010-12-04
        • 2011-03-13
        • 2020-09-08
        相关资源
        最近更新 更多