2

What's the most direct/efficient way to create all possibilities of dividing one (even) list into two in Haskell? I toyed with splitting all permutations of the list but that would add many extras - all the instances where each half contains the same elements, just in a different order. For example,

[1,2,3,4] should produce something like:

[ [1,2],  [3,4] ]
[ [1,3],  [2,4] ]
[ [1,4],  [2,3] ]

Edit: thank you for your comments -- the order of elements and the type of the result is less important to me than the concept - an expression of all two-groups from one group, where element order is unimportant.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
  • If you're splitting a list into two: do those two smaller lists have to have the same length? What if the original list has an odd length? (Your example doesn't show a list being split into two, it just shows three permutations of the list.) – dave4420 Mar 02 '13 at 19:36
  • @dave4420 thanks for asking...I added the word 'even' to my question. Yes, I'd like the smaller lists to have the same size. my example just shows the divisions merged, [1,2] ++ [3,4], etc. – גלעד ברקן Mar 02 '13 at 19:39
  • 2
    So your actual example output should be `[([1,2],[3,4]), ([1,3],[2,4]), ([1,4],[2,3])]`? – dave4420 Mar 02 '13 at 19:49
  • @dave4420 that would indeed be one valid way to express it. Mine is too. – גלעד ברקן Mar 02 '13 at 19:51
  • Are you looking for a function with type `[a] -> [[a]]` (which your example seems to imply), `[a] -> [([a], [a])]` (which my earlier comment implied), or some other type? – dave4420 Mar 02 '13 at 19:56
  • I don't understand the requirements... why is 1 always at the front? – luqui Mar 02 '13 at 20:00
  • @luqui thanks for asking...the order is unimportant...only which elements are in each side, ill try to clarify that – גלעד ברקן Mar 02 '13 at 20:02
  • And also I gather `[1,2,3,4]` is considered equivalent to `[3,4,1,2]`; i.e. you do not distinguish between the first and second side. Correct? – luqui Mar 02 '13 at 20:05
  • @luqui that is correct, I tried make a clearer edit – גלעד ברקן Mar 02 '13 at 20:06

4 Answers4

4

To find all partitions of a non-empty list (of even length n) into two equal-sized parts, we can, to avoid repetitions, posit that the first element shall be in the first part. Then it remains to find all ways to split the tail of the list into one part of length n/2 - 1 and one of length n/2.

-- not to be exported
splitLen :: Int -> Int -> [a] -> [([a],[a])]
splitLen 0 _ xs = [([],xs)]
splitLen _ _ [] = error "Oops"
splitLen k l ys@(x:xs)
    | k == l    = [(ys,[])]
    | otherwise = [(x:us,vs) | (us,vs) <- splitLen (k-1) (l-1) xs]
                  ++ [(us,x:vs) | (us,vs) <- splitLen k (l-1) xs]

does that splitting if called appropriately. Then

partitions :: [a] -> [([a],[a])]
partitions [] = [([],[])]
partitions (x:xs)
    | even len  = error "Original list with odd length"
    | otherwise = [(x:us,vs) | (us,vs) <- splitLen half len xs]
      where
        len = length xs
        half = len `quot` 2

generates all the partitions without redundantly computing duplicates.

luqui raises a good point. I haven't taken into account the possibility that you'd want to split lists with repeated elements. With those, it gets a little more complicated, but not much. First, we group the list into equal elements (done here for an Ord constraint, for only Eq, that could still be done in O(length²)). The idea is then similar, to avoid repetitions, we posit that the first half contains more elements of the first group than the second (or, if there is an even number in the first group, equally many, and similar restrictions hold for the next group etc.).

repartitions :: Ord a => [a] -> [([a],[a])]
repartitions = map flatten2 . halves . prepare
  where
    flatten2 (u,v) = (flatten u, flatten v)

prepare :: Ord a => [a] -> [(a,Int)]
prepare = map (\xs -> (head xs, length xs)) . group . sort

halves :: [(a,Int)] -> [([(a,Int)],[(a,Int)])]
halves [] = [([],[])]
halves ((a,k):more)
    | odd total = error "Odd number of elements"
    | even k    = [((a,low):us,(a,low):vs) | (us,vs) <- halves more] ++ [normalise ((a,c):us,(a,k-c):vs) | c <- [low + 1 .. min half k], (us,vs) <- choose (half-c) remaining more]
    | otherwise = [normalise ((a,c):us,(a,k-c):vs) | c <- [low + 1 .. min half k], (us,vs) <- choose (half-c) remaining more]
      where
        remaining = sum $ map snd more
        total = k + remaining
        half = total `quot` 2
        low = k `quot` 2
        normalise (u,v) = (nz u, nz v)
        nz = filter ((/= 0) . snd)

choose :: Int -> Int -> [(a,Int)] -> [([(a,Int)],[(a,Int)])]
choose 0 _ xs = [([],xs)]
choose _ _ [] = error "Oops"
choose need have ((a,k):more) = [((a,c):us,(a,k-c):vs) | c <- [least .. most], (us,vs) <- choose (need-c) (have-k) more]
  where
    least = max 0 (need + k - have)
    most  = min need k

flatten :: [(a,Int)] -> [a]
flatten xs = xs >>= uncurry (flip replicate)
Community
  • 1
  • 1
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
4

Here's an implementation, closely following the definition.

The first element always goes into the left group. After that, we add the next head element into one, or the other group. If one of the groups becomes too big, there is no choice anymore and we must add all the rest into the the shorter group.

divide :: [a] -> [([a], [a])]
divide []     = [([],[])]
divide (x:xs) = go ([x],[], xs, 1,length xs) []
  where
    go (a,b,   [],     i,j) zs = (a,b) : zs   -- i == lengh a - length b
    go (a,b, s@(x:xs), i,j) zs                -- j == length s
       | i    >= j = (a,b++s) : zs
       | (-i) >= j = (a++s,b) : zs
       | otherwise = go (x:a, b, xs, i+1, j-1) $ go (a, x:b, xs, i-1, j-1) zs

This produces

*Main> divide [1,2,3,4]
[([2,1],[3,4]),([3,1],[2,4]),([1,4],[3,2])]

The limitation of having an even length list is unnecessary:

*Main> divide [1,2,3]
[([2,1],[3]),([3,1],[2]),([1],[3,2])]

(the code was re-written in the "difference-list" style for efficiency: go2 A zs == go1 A ++ zs).

edit: How does this work? Imagine yourself sitting at a pile of stones, dividing it into two. You put the first stone to a side, which one it doesn't matter (so, left, say). Then there's a choice where to put each next stone — unless one of the two piles becomes too small by comparison, and we thus must put all the remaining stones there at once.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • I like it! Not least because it seems concise. maybe I'll even be able to understand it, ha. I'll study it. Thanks! – גלעד ברקן Mar 02 '13 at 21:36
  • @groovy Imagine yourself sitting at a pile of stones, trying to divide it into two. We first put one aside (which side, doesn't matter, let's say it's left). We then take each stone and then we have a choice where to put it. All the while we're keeping an eye on a length difference between the two groups - when the difference becomes equal to the amount of stones that are left in front of us, we have no other choice than to put all of them into a smaller pile. – Will Ness Mar 02 '13 at 21:46
  • that's exactly what I had in mind but did not know how to formulate – גלעד ברקן Mar 02 '13 at 21:55
  • The recursive use of `++` suggests that using [`DList`](http://hackage.haskell.org/package/dlist) instead of `[]` for the list of results would improve performance. – luqui Mar 02 '13 at 22:02
  • the new code gives it about 70% speedup (compiled; none if interpd - measuring `length $ divide [1..x]`, x=20..26). Daniel's code seems to be 3-4 times slower than the old code (5-6x slower than the new). – Will Ness Mar 03 '13 at 00:55
  • @WillNess a generalized version, inspired by your answer: http://stackoverflow.com/questions/15178437/all-possibilities-of-dividing-a-list-in-two-in-haskell/16270189#16270189 – גלעד ברקן Apr 29 '13 at 02:02
3

Daniel Fischer's answer is a good way to solve the problem. I offer a worse (more inefficient) way, but one which more obviously (to me) corresponds to the problem description. I will generate all partitions of the list into two equal length sublists, then filter out equivalent ones according to your definition of equivalence. The way I usually solve problems is by starting like this -- create a solution that is as obvious as possible, then gradually transform it into a more efficient one (if necessary).

import Data.List (sort, nubBy, permutations)

type Partition a = ([a],[a])

-- Your notion of equivalence (sort to ignore the order)
equiv :: (Ord a) => Partition a -> Partition a -> Bool
equiv p q = canon p == canon q
    where
    canon (xs,ys) = sort [sort xs, sort ys]

-- All ordered partitions
partitions :: [a] -> [Partition a]
partitions xs = map (splitAt l) (permutations xs)
    where
    l = length xs `div` 2

-- All partitions filtered out by the equivalence
equivPartitions :: (Ord a) => [a] -> [Partition a]
equivPartitions = nubBy equiv . partitions

Testing

>>> equivPartitions [1,2,3,4]
[([1,2],[3,4]),([3,2],[1,4]),([3,1],[2,4])]

Note

After using QuickCheck to test the equivalence of this implementation with Daniel's, I found an important difference. Clearly, mine requires an (Ord a) constraint and his does not, and this hints at what the difference would be. In particular, if you give his [0,0,0,0], you will get a list with three copies of ([0,0],[0,0]), whereas mine will give only one copy. Which of these is correct was not specified; Daniel's is natural when considering the two output lists to be ordered sequences (which is what that type is usually considered to be), mine is natural when considering them as sets or bags (which is how this question seemed to be treating them).

Splitting The Difference

It is possible to get from an implementation that requires Ord to one that doesn't, by operating on the positions rather than the values in a list. I came up with this transformation -- an idea which I believe originates with Benjamin Pierce in his work on bidirectional programming.

import Data.Traversable
import Control.Monad.Trans.State

data Labelled a = Labelled { label :: Integer, value :: a }

instance Eq (Labelled a) where
    a == b = compare a b == EQ
instance Ord (Labelled a) where
    compare a b = compare (label a) (label b)

labels :: (Traversable t) => t a -> t (Labelled a)
labels t = evalState (traverse trav t) 0
    where
    trav x = state (\i -> i `seq` (Labelled i x, i + 1))

onIndices :: (Traversable t, Functor u)
          => (forall a. Ord a => t a -> u a)
          -> forall b. t b -> u b
onIndices f = fmap value . f . labels

Using onIndices on equivPartitions wouldn't speed it up at all, but it would allow it to have the same semantics as Daniel's (up to equiv of the results) without the constraint, and with my more naive and obvious way of expressing it -- and I just thought it was an interesting way to get rid of the constraint.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • luqui, please try to do this: `length $ equivPartitions [1..10]` (if you get an answer, please let me know the specs on your computer). Then try to do this: `length $ divide [1..10]` (divide is from the answer by Will Ness). Nubbing the permutations was my idea too, except for what happens when the list gets bigger. Something like: `nubBy (\x y -> sort (drop 2 x) == sort (drop 2 y) || sort (drop 2 x) == sort (drop 2 $ reverse y)) (take 12 $ permutations [1..4])` (I had another, probably less efficient, way to nub at that time since I didn't know about nubBy..thanks for introducing it to me!) – גלעד ברקן Mar 03 '13 at 15:35
  • @groovy btw I compared my code with the first code of Daniel Fischer; testing the length as you show; I got equal results. But I didn't try luqui's code. off-topic: for a taste of `nubBy`, try `nubBy (((==0).).rem) [2..]`. – Will Ness Mar 03 '13 at 15:58
  • 1
    @WillNess is that the shortest prime generator on the planet? – גלעד ברקן Mar 03 '13 at 16:18
  • @groovy in Haskell, probably. In J they write `p` or something. :) – Will Ness Mar 03 '13 at 21:35
  • @groovy shorter and independent of arguments' ordering: `nubBy (((>1).).gcd) [2..]`. was hiding [on haskellwiki](http://www.haskell.org/haskellwiki/index.php?title=Blow_your_mind&diff=prev&oldid=20101). – Will Ness Sep 13 '13 at 09:06
  • @WillNess firstly, if you had known about `gcd`, I would be surprised if you didn't think of that one already; secondly, I don't know, if you have a built-in function `gcd`, might you not just as well have a function `prime`...? – גלעד ברקן Sep 13 '13 at 16:23
  • @groovy it's built in: ``Prelude> :i gcd`` -- ``gcd :: (Integral a) => a -> a -> a -- Defined in GHC.Real``. _ I _ didn't think up neither of these versions by myself. :) – Will Ness Sep 13 '13 at 17:43
  • 1
    @WillNess Re: policy of automatic bans on meta...seems like it ought to be different for stackoverflow and the meta version; on the latter perhaps a looser ban-policy would be more appropriate. – גלעד ברקן Sep 13 '13 at 18:13
2

My own generalized version, added much later, inspired by Will's answer:

import Data.Map (adjust, fromList, toList)
import Data.List (groupBy, sort)

divide xs n evenly = divide' xs (zip [0..] (replicate n [])) where
  evenPSize = div (length xs) n
  divide' []     result = [result]
  divide' (x:xs) result = do
    index <- indexes
    divide' xs (toList $ adjust (x :) index (fromList result)) where
      notEmptyBins = filter (not . null . snd) $ result
      partlyFullBins | evenly == "evenly" = map fst . filter ((<evenPSize) . length . snd) $ notEmptyBins
                     | otherwise          = map fst notEmptyBins
      indexes = partlyFullBins 
             ++ if any (null . snd) result
                   then map fst . take 1 . filter (null . snd) $ result
                   else if null partlyFullBins
                           then map fst. head . groupBy (\a b -> length (snd a) == length (snd b)) . sort $ result
                           else []
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61