diff --git a/Data/Partition.hs b/Data/Partition.hs
new file mode 100644
--- /dev/null
+++ b/Data/Partition.hs
@@ -0,0 +1,78 @@
+--------------------------------------------------------------------------
+-- |
+-- Module               : Data.Parition
+-- Copyright            : (c) Luke Palmer, 2013
+-- License              : BSD3
+--
+-- Maintainer           : Luke Palmer <lrpalmer@gmail.com>
+-- Stability            : experimental
+-- Portability          : portable
+--
+-- Disjoint set data structure -- @Partition a@ maintains a collection of
+-- disjoint sets of type @a@, with the ability to find which set a particular
+-- item belongs to and the ability to merge any two such sets into one.
+---------------------------------------------------------------------------
+
+module Data.Partition 
+    ( Partition, discrete, empty, join, find )
+ where
+
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+import Data.Maybe (fromMaybe)
+
+-- | A Partition of @a@: represents a collection of disjoint sets of @a@ whose
+-- union includes every element of @a@.  Semantics: @[[Partition a]] = P(P(a))@
+-- where @P@ is the power set operation.
+data Partition a 
+    = Partition { forwardMap :: Map.Map a a, backwardMap :: Map.Map a (Set.Set a) }
+    deriving (Eq, Ord)  -- Since the representative is always the least element,
+                        -- we have a canonical representation and Eq is meaningful.
+                        -- Ord may not mean anything, but at least there some 
+                        -- computable total ordering on Partitions, and that is helpful
+                        -- sometimes.
+
+-- | A partition in which every element of @a@ is in its own set.  Semantics:
+-- @[[discrete]] = { { x } | x in a }@
+discrete :: Partition a
+discrete = Partition Map.empty Map.empty
+
+-- | Synonym for @discrete@.
+empty :: Partition a
+empty = discrete
+
+-- | @join x y@ merges the two sets containing @x@ and @y@ into a single set.  Semantics:
+-- @[[join x y p]] = (p `minus` find x `minus` find y) `union` { find x `union` find y }@
+join :: (Ord a) => a -> a -> Partition a -> Partition a
+join x y p = case compare x' y' of
+                 LT -> go x' y'
+                 EQ -> p
+                 GT -> go y' x'
+    where
+    x' = repr p x
+    y' = repr p y
+
+    go into other = Partition { 
+                        forwardMap = compose [ Map.insert o into | o <- Set.toList otherSrc ] (forwardMap p),
+                        backwardMap = Map.insert into (Set.union (reprFind p into) otherSrc) 
+                                    . Map.delete other
+                                    $ backwardMap p
+                    }
+        where
+        otherSrc = reprFind p other
+
+-- | @find p x@ finds the set that the element @x@ is associated with.  Semantics: 
+-- @[[find p x]] = the unique s in p such that x in s@.
+find :: (Ord a) => Partition a -> a -> Set.Set a
+find p = reprFind p . repr p
+
+-- Find the representative element for an element.
+repr :: (Ord a) => Partition a -> a -> a
+repr p x = fromMaybe x (Map.lookup x (forwardMap p))
+
+-- Find the set that x is in given that x is already a representative element.
+reprFind :: (Ord a) => Partition a -> a -> Set.Set a
+reprFind p x = fromMaybe (Set.singleton x) (Map.lookup x (backwardMap p))
+
+compose :: [a -> a] -> a -> a
+compose = foldr (.) id
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Luke Palmer
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Luke Palmer nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/data-partition.cabal b/data-partition.cabal
new file mode 100644
--- /dev/null
+++ b/data-partition.cabal
@@ -0,0 +1,21 @@
+-- Initial data-partition.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                data-partition
+version:             0.1.0.0
+synopsis:            A pure disjoint set (union find) data structure
+-- description:         
+homepage:            https://github.com/luqui/data-partition
+license:             BSD3
+license-file:        LICENSE
+author:              Luke Palmer
+maintainer:          lrpalmer@gmail.com
+-- copyright:           
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.8
+
+library
+  exposed-modules:     Data.Partition
+  -- other-modules:       
+  build-depends:       base ==4.*, containers
