diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
 # Changelog for multi-containers
 
+## 0.1.1
+
+- Add min/max functions for `Data.Multimap` and `Data.Multimap.Set`.
+
 ## 0.1.0.2
 
 - Remove redundant constraints
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Author name here (c) 2019
+Copyright Ziyang Liu (c) 2019-2020.
 
 All rights reserved.
 
@@ -13,7 +13,7 @@
       disclaimer in the documentation and/or other materials provided
       with the distribution.
 
-    * Neither the name of Author name here nor the names of other
+    * Neither the name of the author nor the names of other
       contributors may be used to endorse or promote products derived
       from this software without specific prior written permission.
 
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/multi-containers.cabal b/multi-containers.cabal
--- a/multi-containers.cabal
+++ b/multi-containers.cabal
@@ -1,11 +1,7 @@
--- This file has been generated from package.yaml by hpack version 0.28.2.
---
--- see: https://github.com/sol/hpack
---
--- hash: 5c8d0a2541fa360ef1903f422222c873e84c5ea3b0fd709475a4ee54c843bbf8
+cabal-version:  2.4
 
 name:           multi-containers
-version:        0.1.0.2
+version:        0.1.1
 synopsis:       A few multimap variants.
 description:    A library that provides a few multimap variants.
 category:       Data Structures
@@ -13,11 +9,12 @@
 bug-reports:    https://github.com/zliu41/multi-containers/issues
 author:         Ziyang Liu <free@cofree.io>
 maintainer:     Ziyang Liu <free@cofree.io>
-copyright:      2019 Ziyang Liu
-license:        BSD3
+copyright:      2019-2020 Ziyang Liu
+license:        BSD-3-Clause
 license-file:   LICENSE
 build-type:     Simple
-cabal-version:  >= 1.10
+tested-with:    GHC==8.10.1, GHC==8.8.2, GHC==8.6.5, GHC==8.4.4
+
 extra-source-files:
     ChangeLog.md
     README.md
@@ -33,27 +30,16 @@
       Data.Multimap.Table
   other-modules:
       Paths_multi_containers
+  autogen-modules:
+      Paths_multi_containers
   hs-source-dirs:
       src
+  ghc-options: -Wall
   build-depends:
       base >=4.7 && <5
     , containers >=0.5.10.2 && <0.7
   default-language: Haskell2010
 
-executable test-gen
-  main-is: Main.hs
-  other-modules:
-      Paths_multi_containers
-  hs-source-dirs:
-      test-gen
-  build-depends:
-      base >=4.7 && <5
-    , containers >=0.5.10.2 && <0.7
-    , directory >=1.3.0.2 && <1.4
-    , extra >=1.6.9 && <1.7
-    , filepath >=1.4.1.2 && <1.5
-  default-language: Haskell2010
-
 test-suite hspec
   type: exitcode-stdio-1.0
   main-is: Main.hs
@@ -62,12 +48,15 @@
       Data.Multimap.TableSpec
       Data.MultimapSpec
       Paths_multi_containers
+  autogen-modules:
+      Paths_multi_containers
   hs-source-dirs:
       test/hspec
-  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       base >=4.7 && <5
     , containers >=0.5.10.2 && <0.7
-    , hspec >=2.4.8 && <2.8
+    , hspec >=2.4.8 && <3
     , multi-containers
   default-language: Haskell2010
+  build-tool-depends: hspec-discover:hspec-discover == 2.*
diff --git a/src/Data/Multimap.hs b/src/Data/Multimap.hs
--- a/src/Data/Multimap.hs
+++ b/src/Data/Multimap.hs
@@ -135,6 +135,14 @@
   , mapMaybeWithKey
   , mapEither
   , mapEitherWithKey
+
+  -- * Min\/Max
+  , lookupMin
+  , lookupMax
+  , lookupLT
+  , lookupGT
+  , lookupLE
+  , lookupGE
   ) where
 
 import           Control.Arrow ((&&&))
@@ -150,7 +158,6 @@
 import           Data.Map.Lazy (Map)
 import qualified Data.Map.Lazy as Map
 import qualified Data.Maybe as Maybe
-import           Data.Semigroup (Semigroup, (<>))
 import           Data.Set (Set)
 
 import Prelude hiding (filter, foldl, foldr, lookup, map, null)
@@ -515,7 +522,7 @@
 foldl :: (a -> b -> a) -> a -> Multimap k b -> a
 foldl = foldlWithKey . (const .)
 
--- | /O(n)/. Fold the key\/value paris in the map using the given
+-- | /O(n)/. Fold the key\/value pairs in the map using the given
 -- right-associative binary operator.
 --
 -- > foldrWithKey (\k a len -> length (show k) + length a + len) 0 (fromList [(1, "hello"), (1, "world"), (20, "!")]) === 15
@@ -524,7 +531,7 @@
   where
     f' = flip . Foldable.foldr . f
 
--- | /O(n)/. Fold the key\/value paris in the map using the given
+-- | /O(n)/. Fold the key\/value pairs in the map using the given
 -- left-associative binary operator.
 --
 -- > foldlWithKey (\len k a -> length (show k) + length a + len) 0 (fromList [(1, "hello"), (1, "world"), (20, "!")]) === 15
@@ -743,3 +750,55 @@
       $ Map.mapWithKey g m
   where
     g k as = Either.partitionEithers $ fmap (f k) (Nel.toList as)
+
+------------------------------------------------------------------------------
+
+-- | /O(log n)/. Return the smallest key and the associated values. Returns 'Nothing'
+-- if the map is empty.
+--
+-- > lookupMin (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (1, NonEmpty.fromList "ac")
+-- > lookupMin (empty :: Multimap Int Char) === Nothing
+lookupMin :: Multimap k a -> Maybe (k, NonEmpty a)
+lookupMin (Multimap (m, _)) = Map.lookupMin m
+
+-- | /O(log n)/. Return the largest key and the associated values. Returns 'Nothing'
+-- if the map is empty.
+--
+-- > lookupMax (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (2, NonEmpty.fromList "c")
+-- > lookupMax (empty :: Multimap Int Char) === Nothing
+lookupMax :: Multimap k a -> Maybe (k, NonEmpty a)
+lookupMax (Multimap (m, _)) = Map.lookupMax m
+
+-- | /O(log n)/. Return the largest key smaller than the given one, and the associated
+-- values, if exist.
+--
+-- > lookupLT 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupLT 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+lookupLT :: Ord k => k -> Multimap k a -> Maybe (k, NonEmpty a)
+lookupLT k (Multimap (m, _)) = Map.lookupLT k m
+
+-- | /O(log n)/. Return the smallest key larger than the given one, and the associated
+-- values, if exist.
+--
+-- > lookupGT 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupGT 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+lookupGT :: Ord k => k -> Multimap k a -> Maybe (k, NonEmpty a)
+lookupGT k (Multimap (m, _)) = Map.lookupGT k m
+
+-- | /O(log n)/. Return the largest key smaller than or equal to the given one, and the associated
+-- values, if exist.
+--
+-- > lookupLE 0 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupLE 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (1, NonEmpty.fromList "a")
+-- > lookupLE 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+lookupLE :: Ord k => k -> Multimap k a -> Maybe (k, NonEmpty a)
+lookupLE k (Multimap (m, _)) = Map.lookupLE k m
+
+-- | /O(log n)/. Return the smallest key larger than or equal to the given one, and the associated
+-- values, if exist.
+--
+-- > lookupGE 6 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupGE 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (5, NonEmpty.fromList "c")
+-- > lookupGE 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+lookupGE :: Ord k => k -> Multimap k a -> Maybe (k, NonEmpty a)
+lookupGE k (Multimap (m, _)) = Map.lookupGE k m
diff --git a/src/Data/Multimap/Set.hs b/src/Data/Multimap/Set.hs
--- a/src/Data/Multimap/Set.hs
+++ b/src/Data/Multimap/Set.hs
@@ -133,6 +133,14 @@
   , mapMaybeWithKey
   , mapEither
   , mapEitherWithKey
+
+  -- * Min\/Max
+  , lookupMin
+  , lookupMax
+  , lookupLT
+  , lookupGT
+  , lookupLE
+  , lookupGE
   ) where
 
 import Prelude hiding (filter, foldl, foldr, lookup, map, null)
@@ -145,7 +153,6 @@
 import           Data.Map.Lazy (Map)
 import qualified Data.Map.Lazy as Map
 import qualified Data.Maybe as Maybe
-import           Data.Semigroup (Semigroup, (<>))
 import           Data.Set (Set)
 import qualified Data.Set as Set
 
@@ -472,7 +479,7 @@
 foldl :: (a -> b -> a) -> a -> SetMultimap k b -> a
 foldl = foldlWithKey . (const .)
 
--- | /O(n)/. Fold the key\/value paris in the map using the given
+-- | /O(n)/. Fold the key\/value pairs in the map using the given
 -- right-associative binary operator.
 --
 -- > foldrWithKey (\k a len -> length (show k) + length a + len) 0 (fromList [(1, "hello"), (1, "world"), (20, "!")]) === 15
@@ -481,7 +488,7 @@
   where
     f' = flip . Set.foldr . f
 
--- | /O(n)/. Fold the key\/value paris in the map using the given
+-- | /O(n)/. Fold the key\/value pairs in the map using the given
 -- left-associative binary operator.
 --
 -- > foldlWithKey (\len k a -> length (show k) + length a + len) 0 (fromList [(1, "hello"), (1, "world"), (20, "!")]) === 15
@@ -683,6 +690,58 @@
       $ Map.mapWithKey g m
   where
     g k = partitionEithers . Set.map (f k)
+
+------------------------------------------------------------------------------
+
+-- | /O(log n)/. Return the smallest key and the associated values. Returns 'Nothing'
+-- if the map is empty.
+--
+-- > lookupMin (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (1, Set.fromList "ac")
+-- > lookupMin (empty :: SetMultimap Int Char) === Nothing
+lookupMin :: SetMultimap k a -> Maybe (k, Set a)
+lookupMin (SetMultimap (m, _)) = Map.lookupMin m
+
+-- | /O(log n)/. Return the largest key and the associated values. Returns 'Nothing'
+-- if the map is empty.
+--
+-- > lookupMax (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (2, Set.fromList "c")
+-- > lookupMax (empty :: SetMultimap Int Char) === Nothing
+lookupMax :: SetMultimap k a -> Maybe (k, Set a)
+lookupMax (SetMultimap (m, _)) = Map.lookupMax m
+
+-- | /O(log n)/. Return the largest key smaller than the given one, and the associated
+-- values, if exist.
+--
+-- > lookupLT 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupLT 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+lookupLT :: Ord k => k -> SetMultimap k a -> Maybe (k, Set a)
+lookupLT k (SetMultimap (m, _)) = Map.lookupLT k m
+
+-- | /O(log n)/. Return the smallest key larger than the given one, and the associated
+-- values, if exist.
+--
+-- > lookupGT 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupGT 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+lookupGT :: Ord k => k -> SetMultimap k a -> Maybe (k, Set a)
+lookupGT k (SetMultimap (m, _)) = Map.lookupGT k m
+
+-- | /O(log n)/. Return the largest key smaller than or equal to the given one, and the associated
+-- values, if exist.
+--
+-- > lookupLE 0 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupLE 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (1, Set.fromList "a")
+-- > lookupLE 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+lookupLE :: Ord k => k -> SetMultimap k a -> Maybe (k, Set a)
+lookupLE k (SetMultimap (m, _)) = Map.lookupLE k m
+
+-- | /O(log n)/. Return the smallest key larger than or equal to the given one, and the associated
+-- values, if exist.
+--
+-- > lookupGE 6 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+-- > lookupGE 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (5, Set.fromList "c")
+-- > lookupGE 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+lookupGE :: Ord k => k -> SetMultimap k a -> Maybe (k, Set a)
+lookupGE k (SetMultimap (m, _)) = Map.lookupGE k m
 
 ------------------------------------------------------------------------------
 -- * Non exported functions
diff --git a/src/Data/Multimap/Table.hs b/src/Data/Multimap/Table.hs
--- a/src/Data/Multimap/Table.hs
+++ b/src/Data/Multimap/Table.hs
@@ -131,7 +131,6 @@
 import           Data.Map (Map)
 import qualified Data.Map as Map
 import qualified Data.Maybe as Maybe
-import           Data.Semigroup (Semigroup, (<>))
 import           Data.Set (Set)
 
 import Prelude hiding (filter, foldl, foldr, lookup, map, null)
diff --git a/test-gen/Main.hs b/test-gen/Main.hs
deleted file mode 100644
--- a/test-gen/Main.hs
+++ /dev/null
@@ -1,49 +0,0 @@
-module Main (main) where
-
-import Data.List.Extra (replace, stripPrefix, trim)
-import Data.Maybe (mapMaybe)
-import System.Directory
-import System.FilePath
-
-import Prelude hiding (mod)
-
-main :: IO ()
-main = do
-  genTestsFor "Data.Multimap"
-  genTestsFor "Data.Multimap.Set"
-  genTestsFor "Data.Multimap.Table"
-
-genTestsFor :: String -> IO ()
-genTestsFor mod = do
-  let inputFile = "src" </> replace "." [pathSeparator] mod <.> "hs"
-      outputFile = "test/hspec" </> (replace "." [pathSeparator] mod ++ "Spec.hs")
-  src <- readFile inputFile
-  createDirectoryIfMissing True (takeDirectory outputFile)
-  let lns = fmap trim (lines src)
-      tests = mapMaybe (stripPrefix "-- > ") lns
-  writeFile outputFile . unlines $ header mod ++ fmap (indent 6) tests
-
-header :: String -> [String]
-header mod =
-  [ "-- Generated code, do not modify by hand. Generate by running \"stack build && stack exec test-gen\"."
-  , ""
-  , "{-# OPTIONS_GHC -w #-}"
-  , "module " ++ mod ++ "Spec where"
-  , ""
-  , "import Test.Hspec"
-  , "import qualified Data.List.NonEmpty as NonEmpty"
-  , "import qualified Data.Map as Map"
-  , "import qualified Data.Set as Set"
-  ] ++ ["import " ++ mod] ++
-  [ ""
-  , "(===) :: (HasCallStack, Show a, Eq a) => a -> a -> Expectation"
-  , "(===) = shouldBe"
-  , ""
-  , "spec :: Spec"
-  , "spec = do"
-  , "  describe \"Testing " ++ mod ++ "\" $ do"
-  , "    it \"\" $ do"
-  ]
-
-indent :: Int -> String -> String
-indent n = (replicate n ' ' ++)
diff --git a/test/hspec/Data/Multimap/SetSpec.hs b/test/hspec/Data/Multimap/SetSpec.hs
--- a/test/hspec/Data/Multimap/SetSpec.hs
+++ b/test/hspec/Data/Multimap/SetSpec.hs
@@ -115,3 +115,17 @@
         === (fromList [(1,'a'),(2,'a')],fromList [(1,'c'),(2,'c')])
       mapEitherWithKey (\k a -> if even k && a < 'b' then Left a else Right a) (fromList [(1,'a'),(1,'c'),(2,'a'),(2,'c')])
         === (fromList [(2,'a')],fromList [(1,'a'),(1,'c'),(2,'c')])
+      lookupMin (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (1, Set.fromList "ac")
+      lookupMin (empty :: SetMultimap Int Char) === Nothing
+      lookupMax (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (2, Set.fromList "c")
+      lookupMax (empty :: SetMultimap Int Char) === Nothing
+      lookupLT 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupLT 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+      lookupGT 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupGT 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+      lookupLE 0 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupLE 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (1, Set.fromList "a")
+      lookupLE 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
+      lookupGE 6 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupGE 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (5, Set.fromList "c")
+      lookupGE 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, Set.fromList "bc")
diff --git a/test/hspec/Data/MultimapSpec.hs b/test/hspec/Data/MultimapSpec.hs
--- a/test/hspec/Data/MultimapSpec.hs
+++ b/test/hspec/Data/MultimapSpec.hs
@@ -125,3 +125,17 @@
         === (fromList [(1,'a'),(2,'a')],fromList [(1,'c'),(2,'c')])
       mapEitherWithKey (\k a -> if even k && a < 'b' then Left a else Right a) (fromList [(1,'a'),(1,'c'),(2,'a'),(2,'c')])
         === (fromList [(2,'a')],fromList [(1,'a'),(1,'c'),(2,'c')])
+      lookupMin (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (1, NonEmpty.fromList "ac")
+      lookupMin (empty :: Multimap Int Char) === Nothing
+      lookupMax (fromList [(1,'a'),(1,'c'),(2,'c')]) === Just (2, NonEmpty.fromList "c")
+      lookupMax (empty :: Multimap Int Char) === Nothing
+      lookupLT 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupLT 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+      lookupGT 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupGT 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+      lookupLE 0 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupLE 1 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (1, NonEmpty.fromList "a")
+      lookupLE 4 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
+      lookupGE 6 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Nothing
+      lookupGE 5 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (5, NonEmpty.fromList "c")
+      lookupGE 2 (fromList [(1,'a'),(3,'b'),(3,'c'),(5,'c')]) === Just (3, NonEmpty.fromList "bc")
