交错两个字符串,保留顺序:功能样式

时间:2016-03-31 20:19:35

标签: haskell functional-programming

this question中,作者提出了一个有趣的编程问题:给定两个字符串,找到可能的' interleaved'那些保留原始字符串顺序的排列。

我在OP的案例中将问题归结为n字符串而非2,并提出:

    
-- charCandidate is a function that finds possible character from given strings.
-- input : list of strings
-- output : a list of tuple, whose first value holds a character 
-- and second value holds the rest of strings with that character removed
-- i.e ["ab", "cd"] -> [('a', ["b", "cd"])] ..

charCandidate xs = charCandidate' xs []
charCandidate' :: [String] -> [String] -> [(Char, [String])]
charCandidate' [] _ = []     
charCandidate' ([]:xs) prev = 
    charCandidate' xs prev
charCandidate' (x@(c:rest):xs) prev =
    (c, prev ++ [rest] ++ xs) : charCandidate' xs (x:prev)

interleavings :: [String] -> [String]
interleavings xs = interleavings' xs []    

-- interleavings is a function that repeatedly applies 'charCandidate' function, to consume
-- the tuple and build permutations.
-- stops looping if there is no more tuple from charCandidate.

interleavings' :: [String] -> String -> [String]
interleavings' xs prev = 
    let candidates = charCandidate xs
        in case candidates of
            [] -> [prev]
            _  -> concat . map (\(char, ys) -> interleavings' ys (prev ++ [char])) $ candidates

-- test case
input :: [String]
input = ["ab", "cd"]    
-- interleavings input == ["abcd","acbd","acdb","cabd","cadb","cdab"]

它有效,但我非常关心代码:

  1. 很难看。没有任何意义!
  2. 显式递归和附加函数参数prev以保存状态
  3. 使用元组作为中间形式
  4. 如何重写上述程序更多" haskellic",简洁,可读,更符合"功能编程"?

5 个答案:

答案 0 :(得分:2)

我想我会这样写。主要思想是将交织创建为一个非确定性过程,选择其中一个输入字符串来启动交错和递归。

在我们开始之前,它将有助于我有无数次使用的实用功能。它提供了一种从列表中选择元素并知道它是哪个元素的便捷方法。这有点像您的adb shell dumpsys meminfo,除了它一次只在一个列表上运行(因此更广泛适用)。

TOTAL - FREE RAM - USED RAM

有了这个,使用list monad很容易做出一些非确定性的选择。从理论上讲,我们的charCandidate'函数可能应该有类似zippers :: [a] -> [([a], a, [a])] zippers = go [] where go xs [] = [] go xs (y:ys) = (xs, y, ys) : go (y:xs) ys 的类型,它承诺每个传入的字符串中至少包含一个字符,但interleavings的语法开销对于简单而言太烦人了像这样运动,所以当违反这个前提条件时,我们只会给出错误的答案。您还可以考虑将其作为辅助函数,并在运行此函数之前从顶级函数中过滤掉空列表。

[NonEmpty a] -> [[a]]

你可以看到它进入ghci:

NonEmpty

答案 1 :(得分:2)

这是迄今为止我提出的最快的实施方式。它成对地交错列表列表。

instanceof

这个可怕的丑陋混乱是我找到交错两个列表的最好方法。它的目的是渐近最优(我相信它是);它不是很漂亮。通过使用专用队列(例如interleavings :: [[a]] -> [[a]] interleavings = foldr (concatMap . interleave2) [[]] 中用于实现Data.List的队列而不是序列)可以改进常数因子,但我不想包含那么多样板。 / p>

inits

答案 2 :(得分:1)

在interleave2上使用foldr

interleave :: [[a]] -> [[a]]
interleave = foldr ((concat .) . map . iL2) [[]]  where 
   iL2 [] ys = [ys]
   iL2 xs [] = [xs]
   iL2 (x:xs) (y:ys) = map (x:) (iL2 xs (y:ys)) ++ map (y:) (iL2 (x:xs) ys)

答案 3 :(得分:0)

另一种方法是使用list monad:

interleavings xs ys = interl xs ys ++ interl ys xs where
  interl [] ys = [ys]
  interl xs [] = [xs]
  interl xs ys = do
    i <- [1..(length xs)]
    let (h, t)  = splitAt i xs
    map (h ++) (interl ys t)

因此,递归部分将在两个列表之间交替,从每个列表中依次获取1到N个元素,然后生成所有可能的组合。有趣地使用列表monad。

编辑:修复了导致重复的错误

编辑:回答dfeuer。在评论字段中执行代码变得很棘手。不使用length的解决方案示例可能如下所示:

interleavings xs ys = interl xs ys ++ interl ys xs where 
  interl [] ys = [ys]
  interl xs [] = [xs]
  interl xs ys = splits xs >>= \(h, t) -> map (h ++) (interl ys t)

splits [] = []
splits (x:xs) = ([x], xs) : map ((h, t) -> (x:h, t)) (splits xs)

拆分功能感觉有点尴尬。可以将takeWhilebreaksplitAt结合使用来替换它,但该解决方案最终也有点尴尬。你有什么建议吗?

(我只是为了让它稍短一点,我摆脱了记号)

答案 4 :(得分:0)

结合现有答案中的最佳想法并添加一些我自己的想法:

import Control.Monad

interleave [] ys = return ys
interleave xs [] = return xs
interleave (x : xs) (y : ys) =
  fmap (x :) (interleave xs (y : ys)) `mplus` fmap (y :) (interleave (x : xs) ys)

interleavings :: MonadPlus m => [[a]] -> m [a]
interleavings = foldM interleave []

这不是你能得到的最快的,但它在一般和简单方面应该是好的。