diff --git a/aoc.cabal b/aoc.cabal
--- a/aoc.cabal
+++ b/aoc.cabal
@@ -20,7 +20,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version:            0.2.0.0
+version:            0.2.1.0
 
 -- A short (one-line) description of the package.
 synopsis:           Utility functions commonly used while solving Advent of Code puzzles
diff --git a/src/Utility/AOC.hs b/src/Utility/AOC.hs
--- a/src/Utility/AOC.hs
+++ b/src/Utility/AOC.hs
@@ -12,6 +12,10 @@
 {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
 {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 {-# OPTIONS_GHC -Wno-type-defaults #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveTraversable #-}
 
 module Utility.AOC (
     -- * Pathfinding algorithms
@@ -63,6 +67,12 @@
     memo2,
     memo3,
     memo4,
+    -- * ComplexL1 datatype
+    ComplexL1 (..),
+    realPart,
+    imagPart,
+    conjugate,
+    magnitude,
     -- * Miscellaneous
     range,
     rangeIntersect,
@@ -74,7 +84,7 @@
 ) where
 
 import qualified Data.HashMap.Strict as HM
-import Data.Hashable (Hashable)
+import Data.Hashable (Hashable (hashWithSalt))
 import qualified Data.Set as S
 import qualified Data.Heap as H
 import Data.List (permutations, genericIndex, groupBy)
@@ -82,6 +92,12 @@
 import System.IO.Unsafe (unsafePerformIO)
 import Control.Concurrent (threadDelay, newMVar, readMVar, modifyMVar_)
 import Data.Function (on)
+import Data.Data (Data)
+import GHC.Generics (Generic, Generic1)
+import Foreign (Storable (sizeOf, peek, poke, peekElemOff, pokeElemOff, alignment), castPtr)
+import qualified GHC.Ptr
+import Control.Monad.Zip (MonadZip (mzipWith))
+import Control.Monad.Fix (MonadFix (mfix))
 
 createMinPrioHeap :: Ord a1 => (a1,a) -> H.MinPrioHeap a1 a
 createMinPrioHeap = H.singleton
@@ -367,3 +383,79 @@
 -- | Converts a list of booleans (parsed as a binary number) to an integer.
 binToDec :: Num a => [Bool] -> a
 binToDec = sum . zipWith (*) (map (2^) [0..]) . map (fromIntegral . fromEnum) . reverse
+
+infix 6 :+
+-- | A @Complex@ number type whose instance requires the constituent type to only be part of @Num@ instead of @RealFloat@.
+-- As a consequence of this, the @abs@ and @magnitude@ functions use the L1 metric instead of the L2 metric (which is quite useful for Advent of Code puzzles).
+-- Also, @signum@ is performed element-wise and can only return one of 8 different values.
+-- This breaks the equality @abs z * signum z == z@ that is true in the Complex datatype.
+-- The functions @mkPolar@, @cis@, @polar@ and @phase@ are unimplemented for obvious reasons.
+-- Unlike @Complex@, Ord and Hashable are supported. The Ord instance treats the values as tuples.
+-- The name of the datatype contains "L1" in order to reflect these changes.
+data ComplexL1 a = a :+ a deriving (Eq, Ord, Read, Show, Data, Generic, Generic1, Functor, Foldable, Traversable)
+
+-- | Real part of a complex number.
+realPart :: ComplexL1 a -> a
+realPart (a :+ _) = a
+-- | Imaginary part of a complex number.
+imagPart :: ComplexL1 a -> a
+imagPart (_ :+ b) = b
+
+-- | Conjugate of a complex number.
+conjugate :: Num a => ComplexL1 a -> ComplexL1 a
+conjugate (a :+ b) = a :+ negate b
+-- | (Taxicab) magnitude of a complex number.
+magnitude :: Num a => ComplexL1 a -> a
+magnitude (a :+ b) = abs a + abs b
+
+instance Num a => Num (ComplexL1 a) where
+    (+) :: Num a => ComplexL1 a -> ComplexL1 a -> ComplexL1 a
+    (a:+b) + (c:+d) = (a+c) :+ (b+d)
+    (*) :: Num a => ComplexL1 a -> ComplexL1 a -> ComplexL1 a
+    (a:+b) * (c:+d) = (a*c - b*d) :+ (b*c + a*d)
+    abs :: Num a => ComplexL1 a -> ComplexL1 a
+    abs (a :+ b) = (abs a + abs b) :+ 0
+    signum :: Num a => ComplexL1 a -> ComplexL1 a
+    signum (a :+ b) = signum a :+ signum b
+    fromInteger :: Num a => Integer -> ComplexL1 a
+    fromInteger n = fromInteger n :+ 0
+    negate :: Num a => ComplexL1 a -> ComplexL1 a
+    negate (a :+ b) = (a :+ b) * ((-1) :+ 0)
+
+instance Hashable a => Hashable (ComplexL1 a) where
+    hashWithSalt :: Hashable a => Int -> ComplexL1 a -> Int
+    hashWithSalt salt (a :+ b) = hashWithSalt (hashWithSalt salt a) b
+
+instance Storable a => Storable (ComplexL1 a) where
+    sizeOf :: Storable a => ComplexL1 a -> Int
+    sizeOf a       = 2 * sizeOf (realPart a)
+    alignment :: Storable a => ComplexL1 a -> Int
+    alignment a    = alignment (realPart a)
+    peek :: Storable a => GHC.Ptr.Ptr (ComplexL1 a) -> IO (ComplexL1 a)
+    peek p           = do
+                        let q = castPtr p
+                        r <- peek q
+                        i <- peekElemOff q 1
+                        return (r :+ i)
+    poke :: Storable a => GHC.Ptr.Ptr (ComplexL1 a) -> ComplexL1 a -> IO ()
+    poke p (r :+ i)  = do
+                        let q = castPtr p
+                        poke q r
+                        pokeElemOff q 1 i
+
+instance Applicative ComplexL1 where
+  pure :: a -> ComplexL1 a
+  pure a = a :+ a
+  (<*>) :: ComplexL1 (a -> b) -> ComplexL1 a -> ComplexL1 b
+  f :+ g <*> a :+ b = f a :+ g b
+  liftA2 :: (a -> b -> c) -> ComplexL1 a -> ComplexL1 b -> ComplexL1 c
+  liftA2 f (x :+ y) (a :+ b) = f x a :+ f y b
+
+instance Monad ComplexL1 where
+  a :+ b >>= f = realPart (f a) :+ imagPart (f b)
+
+instance MonadZip ComplexL1 where
+  mzipWith = liftA2
+
+instance MonadFix ComplexL1 where
+  mfix f = (let a :+ _ = f a in a) :+ (let _ :+ a = f a in a)
