开发者

Apply "permutations" of a function over a list

开发者 https://www.devze.com 2023-01-24 02:25 出处:网络
Creating the permutations of a list or set is simple enough. I need to apply a function to each element of all subsets of all elements in a list, in the order in which they occur. For instance:

Creating the permutations of a list or set is simple enough. I need to apply a function to each element of all subsets of all elements in a list, in the order in which they occur. For instance:

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

The code I have is a monstrous pipeline or expensive computations, and I'm not sure how to proceed, or if it's correct. I'm sure there must be a better way to accomplish this task - perhaps in the list monad - but I'm not sure. This is my code:

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)

The general idea was:

(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开发者_JAVA技巧, 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.

I'm sure there's a better way to do it, though. I just don't know it. This way is expensive, messy, and rather prone to error. The Justs are there because of the intended application.


To do this, you can leverage the fact that lists represent non-deterministic values when using applicatives and monads. It then becomes as simple as:

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

It basically reads as follows: "Map each item in a list to itself and the result of applying f to it. Finally, return a list of all the possible combinations of these two values across the whole list."


If I understand your problem correctly, it's best not to describe it in terms of permutations. Rather, it's closer to generating powersets.

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

Each time you add another member to the head of the list, the powerset doubles in size. The second half of the powerset is exactly like the first, but with x included.

For your problem, the choice is not whether to include or exclude x, but whether to apply or not apply f.

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

This does what your "apply" function does, modulo making a Set out of the result.


Paul's and Heatsink's answers are good, but error out when you try to run them on infinite lists.

Here's a different method that works on both infinite and finite lists:

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)

This works as expected - though you'll note it produces a different order to the permutations than Paul's and Heatsink's

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]]


Here is an alternative phrasing of rampion's infinite-input-handling solution:

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

Then we can define apply in Paul's idiomatic style:

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

Contrast sequenceList with the usual definition of sequence:

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

The order of binding is reversed in sequenceList so that the variations of the first element are the "inner loop", i.e. we vary the head faster than the tail. Varying the end of an infinite list is a waste of time.

The other key change is nonempty, the promise that we won't bind an empty list. If any of the inputs were empty, or if the result of the recursive call to sequenceList were ever empty, then we would be forced to return an empty list. We can't tell in advance whether any of inputs is empty (because there are infinitely many of them to check), so the only way for this function to output anything at all is to promise that they won't be.

Anyway, this is fun subtle stuff. Don't stress about it on your first day :-)

0

精彩评论

暂无评论...
验证码 换一张
取 消