diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,9 @@
 Changelog for Extra
 
+1.1
+    #7, add nubOrd, nubOrdOn, nubOrdBy
+    #6, add groupSortOn and groupSortBy
+    #5, add splitAtEnd
 1.0.1
     Make listFilesAvoid drop trailing path separators before testing
     #3, add a constraint base >= 4.4
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Neil Mitchell 2014.
+Copyright Neil Mitchell 2014-2015.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/extra.cabal b/extra.cabal
--- a/extra.cabal
+++ b/extra.cabal
@@ -1,13 +1,13 @@
 cabal-version:      >= 1.10
 build-type:         Simple
 name:               extra
-version:            1.0.1
+version:            1.1
 license:            BSD3
 license-file:       LICENSE
 category:           Development
 author:             Neil Mitchell <ndmitchell@gmail.com>
 maintainer:         Neil Mitchell <ndmitchell@gmail.com>
-copyright:          Neil Mitchell 2014
+copyright:          Neil Mitchell 2014-2015
 synopsis:           Extra functions I use.
 description:
     A library of extra functions for the standard Haskell libraries. Most functions are simple additions, filling out missing functionality. A few functions are available in later versions of GHC, but this package makes them available back to GHC 7.2.
diff --git a/src/Control/Monad/Extra.hs b/src/Control/Monad/Extra.hs
--- a/src/Control/Monad/Extra.hs
+++ b/src/Control/Monad/Extra.hs
@@ -32,7 +32,7 @@
 whenJust mg f = maybe (pure ()) f mg
 
 -- | The identity function which requires the inner argument to be @()@. Useful for functions
---   with overloaded return times.
+--   with overloaded return types.
 --
 -- > \(x :: Maybe ()) -> unit x == x
 unit :: m () -> m ()
diff --git a/src/Data/List/Extra.hs b/src/Data/List/Extra.hs
--- a/src/Data/List/Extra.hs
+++ b/src/Data/List/Extra.hs
@@ -10,14 +10,16 @@
     -- * String operations
     lower, upper, trim, trimStart, trimEnd, word1,
     -- * Splitting    
-    dropEnd, takeEnd, breakEnd, spanEnd,
+    dropEnd, takeEnd, splitAtEnd, breakEnd, spanEnd,
     dropWhileEnd, dropWhileEnd', takeWhileEnd, stripSuffix,
     wordsBy, linesBy,
     breakOn, breakOnEnd, splitOn, split, chunksOf,
     -- * Basics
     list, uncons, unsnoc, cons, snoc, drop1,
     -- * List operations
-    groupSort, nubOn, groupOn, sortOn,
+    groupSort, groupSortOn, groupSortBy,
+    nubOrd, nubOrdBy, nubOrdOn,
+    nubOn, groupOn, sortOn,
     disjoint, allSame, anySame,
     repeatedly, for, firstJust,
     concatUnzip, concatUnzip3,
@@ -153,6 +155,19 @@
           f _ _ = []
 
 
+-- | @'splitAtEnd' n xs@ returns a split where the second element tries to
+--   contain @n@ elements.
+--
+-- > splitAtEnd 3 "hello" == ("he","llo")
+-- > splitAtEnd 3 "he"    == ("", "he")
+-- > \i xs -> uncurry (++) (splitAt i xs) == xs
+-- > \i xs -> splitAtEnd i xs == (dropEnd i xs, takeEnd i xs)
+splitAtEnd :: Int -> [a] -> ([a], [a])
+splitAtEnd i xs = f xs (drop i xs)
+    where f (x:xs) (y:ys) = first (x:) $ f xs ys
+          f xs _ = ([], xs)
+
+
 -- | A merging of 'unzip' and 'concat'.
 --
 -- > concatUnzip [("a","AB"),("bc","C")] == ("abc","ABC")
@@ -244,6 +259,7 @@
 nubOn :: Eq b => (a -> b) -> [a] -> [a]
 nubOn f = map snd . nubBy ((==) `on` fst) . map (\x -> let y = f x in y `seq` (y, x))
 
+
 -- | A combination of 'group' and 'sort'.
 --
 -- > groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,"t"),(2,"es"),(3,"t")]
@@ -252,7 +268,19 @@
 groupSort :: Ord k => [(k, v)] -> [(k, [v])]
 groupSort = map (\x -> (fst $ head x, map snd x)) . groupOn fst . sortOn fst
 
+-- | A combination of 'group' and 'sort', using a part of the value to compare on.
+--
+-- > groupSortOn length ["test","of","sized","item"] == [["of"],["test","item"],["sized"]]
+groupSortOn :: Ord b => (a -> b) -> [a] -> [[a]]
+groupSortOn f = map (map snd) . groupBy ((==) `on` fst) . sortBy (compare `on` fst) . map (f &&& id)
 
+-- | A combination of 'group' and 'sort', using a predicate to compare on.
+--
+-- > groupSortBy (compare `on` length) ["test","of","sized","item"] == [["of"],["test","item"],["sized"]]
+groupSortBy :: (a -> a -> Ordering) -> [a] -> [[a]]
+groupSortBy f = groupBy (\a b -> f a b == EQ) . sortBy f
+
+
 -- | Merge two lists which are assumed to be ordered.
 --
 -- > merge "ace" "bd" == "abcde"
@@ -449,3 +477,70 @@
 chunksOf :: Int -> [a] -> [[a]]
 chunksOf i xs | i <= 0 = error $ "chunksOf, number must be positive, got " ++ show i
 chunksOf i xs = repeatedly (splitAt i) xs
+
+
+-- | /O(n log n)/. The 'nubOrd' function removes duplicate elements from a list.
+-- In particular, it keeps only the first occurrence of each element.
+-- Unlike the standard 'nub' operator, this version requires an 'Ord' instance
+-- and consequently runs asymptotically faster.
+--
+-- > nubOrd "this is a test" == "this ae"
+-- > nubOrd (take 4 ("this" ++ undefined)) == "this"
+-- > \xs -> nubOrd xs == nub xs
+nubOrd :: Ord a => [a] -> [a]
+nubOrd = nubOrdBy compare
+
+-- | A version of 'nubOrd' which operates on a portion of the value.
+--
+-- > nubOrdOn length ["a","test","of","this"] == ["a","test","of"]
+nubOrdOn :: Ord b => (a -> b) -> [a] -> [a]
+nubOrdOn f = map snd . nubOrdBy (compare `on` fst) . map (f &&& id)
+
+-- | A version of 'nubOrd' with a custom predicate.
+--
+-- > nubOrdBy (compare `on` length) ["a","test","of","this"] == ["a","test","of"]
+nubOrdBy :: (a -> a -> Ordering) -> [a] -> [a]
+nubOrdBy cmp xs = f E xs
+    where f seen [] = []
+          f seen (x:xs) | memberRB cmp x seen = f seen xs
+                        | otherwise = x : f (insertRB cmp x seen) xs
+
+---------------------------------------------------------------------
+-- OKASAKI RED BLACK TREE
+-- Taken from http://www.cs.kent.ac.uk/people/staff/smk/redblack/Untyped.hs
+
+data Color = R | B deriving Show
+data RB a = E | T Color (RB a) a (RB a) deriving Show
+
+{- Insertion and membership test as by Okasaki -}
+insertRB :: (a -> a -> Ordering) -> a -> RB a -> RB a
+insertRB cmp x s =
+    T B a z b
+    where
+    T _ a z b = ins s
+    ins E = T R E x E
+    ins s@(T B a y b) = case cmp x y of
+        LT -> balance (ins a) y b
+        GT -> balance a y (ins b)
+        EQ -> s
+    ins s@(T R a y b) = case cmp x y of
+        LT -> T R (ins a) y b
+        GT -> T R a y (ins b)
+        EQ -> s
+
+memberRB :: (a -> a -> Ordering) -> a -> RB a -> Bool
+memberRB cmp x E = False
+memberRB cmp x (T _ a y b) = case cmp x y of
+    LT -> memberRB cmp x a
+    GT -> memberRB cmp x b
+    EQ -> True
+
+{- balance: first equation is new,
+   to make it work with a weaker invariant -}
+balance :: RB a -> a -> RB a -> RB a
+balance (T R a x b) y (T R c z d) = T R (T B a x b) y (T B c z d)
+balance (T R (T R a x b) y c) z d = T R (T B a x b) y (T B c z d)
+balance (T R a x (T R b y c)) z d = T R (T B a x b) y (T B c z d)
+balance a x (T R b y (T R c z d)) = T R (T B a x b) y (T B c z d)
+balance a x (T R (T R b y c) z d) = T R (T B a x b) y (T B c z d)
+balance a x b = T B a x b
diff --git a/src/Extra.hs b/src/Extra.hs
--- a/src/Extra.hs
+++ b/src/Extra.hs
@@ -20,7 +20,7 @@
     modifyIORef', writeIORef', atomicModifyIORef', atomicWriteIORef, atomicWriteIORef',
     -- * Data.List.Extra
     -- | Extra functions available in @"Data.List.Extra"@.
-    lower, upper, trim, trimStart, trimEnd, word1, dropEnd, takeEnd, breakEnd, spanEnd, dropWhileEnd, dropWhileEnd', takeWhileEnd, stripSuffix, wordsBy, linesBy, breakOn, breakOnEnd, splitOn, split, chunksOf, list, uncons, unsnoc, cons, snoc, drop1, groupSort, nubOn, groupOn, sortOn, disjoint, allSame, anySame, repeatedly, for, firstJust, concatUnzip, concatUnzip3, replace, merge, mergeBy,
+    lower, upper, trim, trimStart, trimEnd, word1, dropEnd, takeEnd, splitAtEnd, breakEnd, spanEnd, dropWhileEnd, dropWhileEnd', takeWhileEnd, stripSuffix, wordsBy, linesBy, breakOn, breakOnEnd, splitOn, split, chunksOf, list, uncons, unsnoc, cons, snoc, drop1, groupSort, groupSortOn, groupSortBy, nubOrd, nubOrdBy, nubOrdOn, nubOn, groupOn, sortOn, disjoint, allSame, anySame, repeatedly, for, firstJust, concatUnzip, concatUnzip3, replace, merge, mergeBy,
     -- * Data.Tuple.Extra
     -- | Extra functions available in @"Data.Tuple.Extra"@.
     first, second, (***), (&&&), dupe, both, fst3, snd3, thd3,
diff --git a/test/TestGen.hs b/test/TestGen.hs
--- a/test/TestGen.hs
+++ b/test/TestGen.hs
@@ -84,6 +84,10 @@
     testGen "\\i xs -> dropEnd i xs `isPrefixOf` xs" $ \i xs -> dropEnd i xs `isPrefixOf` xs
     testGen "\\i xs -> length (dropEnd i xs) == max 0 (length xs - max 0 i)" $ \i xs -> length (dropEnd i xs) == max 0 (length xs - max 0 i)
     testGen "\\i -> take 3 (dropEnd 5 [i..]) == take 3 [i..]" $ \i -> take 3 (dropEnd 5 [i..]) == take 3 [i..]
+    testGen "splitAtEnd 3 \"hello\" == (\"he\",\"llo\")" $ splitAtEnd 3 "hello" == ("he","llo")
+    testGen "splitAtEnd 3 \"he\"    == (\"\", \"he\")" $ splitAtEnd 3 "he"    == ("", "he")
+    testGen "\\i xs -> uncurry (++) (splitAt i xs) == xs" $ \i xs -> uncurry (++) (splitAt i xs) == xs
+    testGen "\\i xs -> splitAtEnd i xs == (dropEnd i xs, takeEnd i xs)" $ \i xs -> splitAtEnd i xs == (dropEnd i xs, takeEnd i xs)
     testGen "concatUnzip [(\"a\",\"AB\"),(\"bc\",\"C\")] == (\"abc\",\"ABC\")" $ concatUnzip [("a","AB"),("bc","C")] == ("abc","ABC")
     testGen "concatUnzip3 [(\"a\",\"AB\",\"\"),(\"bc\",\"C\",\"123\")] == (\"abc\",\"ABC\",\"123\")" $ concatUnzip3 [("a","AB",""),("bc","C","123")] == ("abc","ABC","123")
     testGen "takeWhileEnd even [2,3,4,6] == [4,6]" $ takeWhileEnd even [2,3,4,6] == [4,6]
@@ -104,6 +108,8 @@
     testGen "groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,\"t\"),(2,\"es\"),(3,\"t\")]" $ groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,"t"),(2,"es"),(3,"t")]
     testGen "\\xs -> map fst (groupSort xs) == sort (nub (map fst xs))" $ \xs -> map fst (groupSort xs) == sort (nub (map fst xs))
     testGen "\\xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)" $ \xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)
+    testGen "groupSortOn length [\"test\",\"of\",\"sized\",\"item\"] == [[\"of\"],[\"test\",\"item\"],[\"sized\"]]" $ groupSortOn length ["test","of","sized","item"] == [["of"],["test","item"],["sized"]]
+    testGen "groupSortBy (compare `on` length) [\"test\",\"of\",\"sized\",\"item\"] == [[\"of\"],[\"test\",\"item\"],[\"sized\"]]" $ groupSortBy (compare `on` length) ["test","of","sized","item"] == [["of"],["test","item"],["sized"]]
     testGen "merge \"ace\" \"bd\" == \"abcde\"" $ merge "ace" "bd" == "abcde"
     testGen "\\xs ys -> merge (sort xs) (sort ys) == sort (xs ++ ys)" $ \xs ys -> merge (sort xs) (sort ys) == sort (xs ++ ys)
     testGen "replace \"el\" \"_\" \"Hello Bella\" == \"H_lo B_la\"" $ replace "el" "_" "Hello Bella" == "H_lo B_la"
@@ -153,6 +159,11 @@
     testGen "chunksOf 3 \"mytest\"  == [\"myt\",\"est\"]" $ chunksOf 3 "mytest"  == ["myt","est"]
     testGen "chunksOf 8 \"\"        == []" $ chunksOf 8 ""        == []
     testGen "chunksOf 0 \"test\"    == undefined" $ erroneous $ chunksOf 0 "test"
+    testGen "nubOrd \"this is a test\" == \"this ae\"" $ nubOrd "this is a test" == "this ae"
+    testGen "nubOrd (take 4 (\"this\" ++ undefined)) == \"this\"" $ nubOrd (take 4 ("this" ++ undefined)) == "this"
+    testGen "\\xs -> nubOrd xs == nub xs" $ \xs -> nubOrd xs == nub xs
+    testGen "nubOrdOn length [\"a\",\"test\",\"of\",\"this\"] == [\"a\",\"test\",\"of\"]" $ nubOrdOn length ["a","test","of","this"] == ["a","test","of"]
+    testGen "nubOrdBy (compare `on` length) [\"a\",\"test\",\"of\",\"this\"] == [\"a\",\"test\",\"of\"]" $ nubOrdBy (compare `on` length) ["a","test","of","this"] == ["a","test","of"]
     testGen "first succ (1,\"test\") == (2,\"test\")" $ first succ (1,"test") == (2,"test")
     testGen "second reverse (1,\"test\") == (1,\"tset\")" $ second reverse (1,"test") == (1,"tset")
     testGen "(succ *** reverse) (1,\"test\") == (2,\"tset\")" $ (succ *** reverse) (1,"test") == (2,"tset")
diff --git a/test/TestUtil.hs b/test/TestUtil.hs
--- a/test/TestUtil.hs
+++ b/test/TestUtil.hs
@@ -15,6 +15,7 @@
 import Extra as X
 import Control.Applicative as X
 import Control.Monad as X
+import Data.Function as X
 import Data.List as X
 import Data.Char as X
 import Data.Tuple as X
