在列表中应用函数的“排列”

时间:2010-11-10 01:23:16

标签: list haskell

创建列表或集合的排列非常简单。我需要按照它们出现的顺序将一个函数应用于列表中所有元素的所有子集的每个元素。例如:

apply f [x,y] = { [x,y], [f x, y], [x, f y], [f x, f y] }

我的代码是一个可怕的管道或昂贵的计算,我不知道如何继续,或者它是否正确。我确信必须有更好的方法来完成这项任务 - 也许在monad列表中 - 但我不确定。这是我的代码:

apply :: Ord a => (a -> Maybe a) -> [a] -> Set [a]
apply p xs = let box = take (length xs + 1) . map (take $ length xs) in
  (Set.fromList . map (catMaybes . zipWith (flip ($)) xs) . concatMap permutations
   . box . map (flip (++) (repeat Just)) . flip iterate []) ((:) p)

一般的想法是:

(1) make the list 
      [[], [f], [f,f], [f,f,f], ... ]
(2) map (++ repeat Just) over the list to obtain
      [[Just, Just, Just, Just, ... ],
       [f   , Just, Just, Just, ... ],
       [f   , f   , Just, Just, ... ],
                                ... ]
(3) find all permutations of each list in (2) shaved to the length of the input list        
(4) apply the permuted lists to the original list, garnering all possible applications
    of the function f to each (possibly empty) subset of the original list, preserving
    the original order.
但是,我确信有更好的方法可以做到这一点。我只是不知道。这种方式昂贵,杂乱,而且容易出错。由于预期的应用,Justs在那里。

4 个答案:

答案 0 :(得分:14)

为此,您可以利用列表在使用applicatives和monad时表示非确定性值的事实。然后变得如此简单:

apply f = mapM (\x -> [x, f x])

它基本上如下所示:“将列表中的每个项目映射到自身以及将f应用于它的结果。最后,返回整个列表中这两个值的所有可能组合的列表。”

答案 1 :(得分:6)

如果我正确理解你的问题,最好不要用排列来描述它。相反,它更接近于生成powersets。

powerset (x:xs) = let pxs = powerset xs in pxs ++ map (x :) pxs
powerset []     = [[]]

每次将另一个成员添加到列表的头部时,powerset的大小都会翻倍。 powerset的后半部分与第一部分完全相同,但包括x。

对于您的问题,选择不是包括或排除x,而是选择是否应用f。

powersetapp f (x:xs) = let pxs = powersetapp f xs in map (x:) pxs ++ map (f x:) pxs
powersetapp f []     = [[]]

这就是你的“应用”功能所做的事情,以模数形式设置结果。

答案 2 :(得分:3)

Paul和Heatsink的答案很好,但是当你尝试在无限列表上运行它们时会出错。

这是一个适用于无限和有限列表的不同方法:

apply _ [] = [ [] ]
apply f (x:xs) = (x:ys):(x':ys):(double yss)
  where x' = f x
        (ys:yss) = apply f xs
        double [] = []
        double (ys:yss) = (x:ys):(x':ys):(double yss)

这可以按预期工作 - 虽然你会注意到它产生的排列顺序不同于Paul和Heatsink的

ghci> -- on an infinite list
ghci> map (take 4) $ take 16 $ apply (+1) [0,0..]
[[0,0,0,0],[1,0,0,0],[0,1,0,0],[1,1,0,0],[0,0,1,0],...,[1,1,1,1]]
ghci> -- on a finite list
ghci> apply (+1) [0,0,0,0]
[[0,0,0,0],[1,0,0,0],[0,1,0,0],[1,1,0,0],[0,0,1,0],...,[1,1,1,1]]

答案 3 :(得分:2)

以下是对rampion无限输入处理解决方案的另一种措辞:

-- sequence a list of nonempty lists
sequenceList :: [[a]] -> [[a]]
sequenceList [] = [[]]
sequenceList (m:ms) = do
    xs <- nonempty (sequenceList ms)
    x  <- nonempty m
    return (x:xs)
  where
    nonempty ~(x:xs) = x:xs

然后我们可以用保罗的习惯风格来定义apply

apply f = sequenceList . map (\x -> [x, f x])

sequenceListsequence的通常定义进行对比:

sequence :: (Monad m) => [m a] -> m [a]
sequence [] = [[]]
sequence (m:ms) = do
    x <- m
    xs <- sequence ms
    return (x:xs)

sequenceList中的绑定顺序相反,因此第一个元素的变化是“内环”,即我们比尾部更快地改变头部。改变无限列表的结尾是浪费时间。

另一个关键变化是nonempty,我们不会绑定空列表的承诺。如果任何输入为空,或者对sequenceList的递归调用的结果是空的,那么我们将被迫返回一个空列表。我们无法事先告诉任何输入是否为空(因为它们中有无数的要检查),所以这个函数输出任何东西的唯一方法就是保证它们不会。

无论如何,这是有趣的微妙的东西。不要在第一天对此施加压力: - )