diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
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/fastedit.cabal b/fastedit.cabal
new file mode 100644
--- /dev/null
+++ b/fastedit.cabal
@@ -0,0 +1,48 @@
+-- Initial fastedit.cabal generated by cabal init.  For further
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                fastedit
+version:             0.1.0.0
+synopsis:            find nearest neighbours by edit-distance
+description:         Use faroo's deletion algorithm to generate possible
+                     autocorrections efficiently
+license:             MIT
+license-file:        LICENSE
+author:              Mark Wotton
+maintainer:          mwotton@gmail.com
+build-type:          Simple
+cabal-version:       >=1.10
+category:            Text
+source-repository head
+  type: git
+  location: http://github.com/mwotton/fastedit.git
+
+library
+  exposed-modules:     Text.FastEdit
+  build-depends:       base >=4.7 && <4.8,
+                       base-prelude,
+                       unordered-containers,
+                       containers,
+                       safe,
+                       bytestring,
+                       hashable
+  ghc-prof-options: -fprof-auto
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+
+test-suite tests
+  default-language:    Haskell2010
+  hs-source-dirs: test
+  main-is:       Spec.hs
+  type: exitcode-stdio-1.0
+  ghc-options: -Wall -threaded -rtsopts
+  ghc-prof-options: -fprof-auto
+  build-depends: base
+               , fastedit
+               , base-prelude
+               , hspec
+               , edit-distance
+               , QuickCheck
+               , file-embed
+               , bytestring
diff --git a/src/Text/FastEdit.hs b/src/Text/FastEdit.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/FastEdit.hs
@@ -0,0 +1,51 @@
+{-# LANGUAGE TupleSections #-}
+
+module Text.FastEdit where
+
+import           BasePrelude
+import qualified Data.ByteString.Lazy.Char8 as BL
+import           Data.Hashable              (Hashable)
+import qualified Data.HashMap.Strict        as HM
+import qualified Data.HashSet               as HS
+import qualified Data.Sequence              as Seq
+import           Prelude                    ()
+import           Safe                       (atMay)
+
+cantorise :: (a -> b -> [c]) -> [a] -> [b] -> [c]
+cantorise f as bs = concat $ mapMaybe
+                    (\(ai,bi) -> do
+                        a <- atMay as ai
+                        b <- atMay bs bi
+                        return $ f a b) $ take (length as * length bs) antiDiagonals
+
+antiDiagonals :: [(Int, Int)]
+antiDiagonals = concatMap anti [0..]
+  where anti n = zip [0..n] (reverse [0..n])
+
+buildDict :: Int -> [String] -> String -> [String]
+buildDict n dictWords = \s -> nub $ cantorise searchDict (deletions s) dicts
+  where
+    searchDict candidates dict = concat $ mapMaybe (`HM.lookup` dict) candidates
+    dicts = map (fmap HS.toList . builder) $ transpose $ map (take n . tagged) dictWords
+    builder = HM.fromListWith HS.union . concatMap (\(a,as) -> map (,HS.singleton a) as)
+    tagged s = map (s,) (deletions s)
+
+deletions :: String -> [[BL.ByteString]]
+deletions s = map (ordNub . deleteN s) [0..(length s)]
+
+deleteN :: String -> Int -> [BL.ByteString]
+deleteN s n = map BL.pack $ toList $ go s n
+  where
+    go :: String -> Int -> Seq.Seq String
+    go s 0 = Seq.singleton s
+    go (x:xs) n = go xs (n-1) <> fmap (x:) (go xs n)
+    go [] _ = Seq.empty
+
+
+-- ordNub :: (Hashable t, Eq t) => [t] -> [t]
+ordNub :: (Hashable t, Eq t) => [t] -> [t]
+ordNub = go HS.empty
+  where
+    go _ [] = []
+    go s (x:xs) = if x `HS.member` s then go s xs
+                  else x : go (HS.insert x s) xs
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+-- file test/Spec.hs
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
