5

I'm trying to write a Haskell program that could return the partition set of a user defined set. The partition of a set S is defined as a set of nonempty, pairwise disjoint subsets of S whose union is S. So, [1,2,3] returns [[[2],[3,1]],[[2,1],[3]],[[3,2,1]],[[1],[3,2]],[[1],[2],[3]]]. I think I can utilize a different program I wrote a while ago that finds the cartesian product from two sets. So, [1,2,3] ['a', 'b'] returns [(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b')]. However, I'm not sure quite how. I think it would require recursion though, if this can even be adapted properly. Here is the subset code:

type Set a = [a]

isElement :: Eq a => a -> [a] -> Bool
isElement x [] = False
isElement x (y:ys) = if(x==y) then True else isElement x ys

subset :: Eq a => Set a -> Set a -> Bool
subset [] xs = True
subset (y:ys) xs = if(isElement y xs == True)
                  then do subset ys xs
                  else do False
Alexis King
  • 43,109
  • 15
  • 131
  • 205
Matt Robbins
  • 429
  • 2
  • 5
  • 10
  • 1
    Recursively. If you can partition `X`, how can you partition `X ∪ {a}`? – n. m. could be an AI Oct 05 '17 at 23:01
  • Minor style comment: the `if`s you posted seem to be a convoluted way to write, respectively, 1) `x==y || isElement x ys` and 2) `isElement y xs && subset ys xs`. There's no need of `do` here, and `== True` is always redundant. – chi Oct 06 '17 at 17:44

3 Answers3

9

The idea is that in order to find all partitions of set X ∪ {x}, we find parritions of X first. Then add x to each of them in every possible way (that is, add x to the first element of a partition, add x to the second element etc) and take a union of the result.

Here's a rather straightforward implementation:

partitions :: [a] -> [[[a]]]
partitions [] = [[]]
partitions (x:xs) = expand x $ partitions xs where

    expand :: a -> [[[a]]] -> [[[a]]]
    expand x ys = concatMap (extend x) ys

    extend :: a -> [[a]] -> [[[a]]]
    extend x [] = [[[x]]]
    extend x (y:ys) = ((x:y):ys) : map (y:) (extend x ys)

Demo: https://ideone.com/ClYOoQ

4castle
  • 32,613
  • 11
  • 69
  • 106
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

Pseudocode for one recursive algorithm:

If |S| = 1
  Return ∅
Otherwise
  For each nonempty proper subset X ⊂ S
    Let Y = S - X
    Add {X, Y} to R
    For each Z in {partitionSet(X)}
      Add Z ∪ {Y} to R.
  Return R

Since “adding” elements to a list isn’t a very functional idiom, you would want to do those steps with a concatMap or a list comprehension. You might also build R as an accumulating parameter to a tail-recursive function, or as a union of the return values of each step. The proper subsets function is in the Haskell standard library as Data.List.subsequences.

If you have a total ordering on all proper subsets of S, you can use symmetry-breaking to add only partitions that are unique up to permutation. That is, if X > Y, you could add only {X,Y} and not {Y,X}, and only {X,Y,Z} and not {Y,X,Z}. Be careful that you still sub-partition every set in your partition exactly once!

This finds only partition sets of S, if ⋃Z = X and X ∪ Y = S, the union of all sets in Z and Y is S, it returns only sets of nonempty proper subsets of S, and every partition and subpartition is a set difference, hence pairwise disjoint.

Any partition set of cardinality two has the form {X, S-X}, and the algorithm finds it because it tries every possible X. Any partition set of cardinality i>2 has the form {a_1, a_2, ..., a_i}, where {a_1, a_2} is a partition set of {a_1 ⋃ a_2} and {{a_1 ⋃ a_2}, ..., a_i} is a partition set of cardinality i-1, and will be found when subpartitioning the parent node of the search tree. Therefore, by induction, the algorithm finds all partition sets of S.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • 2
    Yeah, I generally don’t write complete solutions of questions that look like homework problems, but I do give hints. – Davislor Oct 06 '17 at 08:29
  • Would you prefer if I deleted that comment? I added it since (1) this was a cool idea, (2) there is already a much simpler solution with the other posted answer and (3) it wasn't immediately clear to me what you meant. – Alec Oct 06 '17 at 14:47
  • @Alec It’s really up to you. – Davislor Oct 06 '17 at 18:13
  • Given that two strangers agreed with you, I've deleted the comment. :) – Alec Oct 06 '17 at 18:14
  • @Alec Well, six strangers upvoted a different complete solution. – Davislor Oct 06 '17 at 18:20
0

Lately, I was playing again with set partitions and haskell. Even though it might be not the fastest and nicest solution it does the job. I found out that using Data.List and the List Monad greatly reduces the amount of code and extents the readability.

Asking myself if there is a neat way to replace foldl by foldr?

Anyway, here is my solution:

module Main where

import Data.List

main :: IO ()
main = print $ allPart 5

insertFront :: Integer -> [[Integer]] -> [[Integer]]
insertFront k (h:t) = [k:h]++t
insertFront k _ = [[k]]

add :: Integer -> [[Integer]] -> [[[Integer]]]
add k part=zipWith (++) (inits part) (map (insertFront k) (tails part))

allPart k = foldl (>>=) [[]] [add i | i<-[1..k]]

I'm also wondering if there is some very short substitute for insertFront using some of the haskell libraries.

Aleph0
  • 5,816
  • 4
  • 29
  • 80