在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"]
它有效,但我非常关心代码:
prev
以保存状态如何重写上述程序更多" haskellic",简洁,可读,更符合"功能编程"?
答案 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)
拆分功能感觉有点尴尬。可以将takeWhile
或break
与splitAt
结合使用来替换它,但该解决方案最终也有点尴尬。你有什么建议吗?
(我只是为了让它稍短一点,我摆脱了记号)
答案 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 []
这不是你能得到的最快的,但它在一般和简单方面应该是好的。