packages feed

multisets-0.1.0.0: src/Data/MultiSet/Natural.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TupleSections #-}

{- |
  Module: Data.MultiSet.Natural
  Description: Multisets with arbitrary-precision Natural multiplicities
  Copyright: (c) 2026 Florian Ragwitz
  License: MIT

  Finite multisets with 'Natural' multiplicities.

  A 'MultiSet' is like a 'Data.Set.Set', except that values may occur more than
  once. The number of occurrences of a value is its /multiplicity/.

  In contrast to "Data.MultiSet", this module represents multiplicities by
  'Natural', allowing them to exceed the range of 'Int'. See
  [Comparison to Data.MultiSet]("Data.MultiSet.Natural#g:comparison")
  for more details on how the two modules differ.

  This module is intended to be imported qualified, to avoid name clashes with
  Prelude functions, e.g.

  > import Data.MultiSet.Natural (MultiSet)
  > import qualified Data.MultiSet.Natural as MS

  When distinct values compare equal under 'Ord', no guarantee is made about
  which value is retained as the representative.
-}
module Data.MultiSet.Natural (
    -- * Comparison to @Data.MultiSet@ #comparison#

    {- |

        This module is broadly similar to "Data.MultiSet". Many common uses are
        source-compatible after changing the module import, and migration is
        usually straightforward.

        The main difference is that this module represents multiplicities using
        'Natural' rather than 'Int', allowing them to grow beyond the range of
        'Int' while also reflecting that multiplicities cannot be negative.

        This module was also motivated in part by a number of longstanding
        issues in "Data.MultiSet", including correctness bugs, alongside
        relatively limited maintenance in recent years.

        The API is intentionally similar rather than identical. Some
        "Data.MultiSet" operations are omitted, this module provides some
        additional operations, and a few concepts are exposed under different
        names.

        If you're missing any particular functions from this module, please
        file a bug report!
    -}

    -- * Types
    MultiSet,
    MaxUnion (..),

    -- * Construction
    empty,
    singleton,
    singletonMany,
    fromMultiplicityList,
    fromList,
    fromSet,
    fromMap,

    -- * Conversion
    toMultiplicityList,
    toList,
    toSet,
    toDistinctList,
    toMap,

    -- * Query
    null,
    member,
    notMember,
    multiplicity,
    size,
    distinctSize,

    -- * Insertion and deletion
    insert,
    insertMany,
    delete,
    deleteMany,
    deleteAll,

    -- * Transformations
    alterMultiplicity,
    alterMultiplicityF,
    setMultiplicity,
    filter,
    filterWithMultiplicity,
    filterA,
    filterWithMultiplicityA,
    partition,
    partitionA,
    partitionWithMultiplicity,
    partitionWithMultiplicityA,
    map,
    mapWithMultiplicity,
    mapMultiplicities,
    mapMaybe,
    mapMaybeWithMultiplicity,
    concatMap,

    -- * Folds

    -- ** Lazy
    foldrWithMultiplicity,
    foldlWithMultiplicity,
    foldMapWithMultiplicity,

    -- ** Strict
    foldlWithMultiplicity',
    foldrWithMultiplicity',

    -- * Traversals
    traverse,
    traverseMaybe,
    traverseWithMultiplicity,
    traverseWithMultiplicity_,
    traverseMaybeWithMultiplicity,

    -- * Combining multisets
    union,
    unions,
    difference,
    symmetricDifference,
    intersection,
    intersections,
    maxUnion,
    cartesianProduct,

    -- * Relations
    isSubsetOf,
    isProperSubsetOf,
    disjoint,

    -- * Ordered queries
    lookupLT,
    lookupLE,
    lookupGT,
    lookupGE,

    -- * Minimum and maximum
    lookupMin,
    lookupMax,
    deleteMin,
    deleteMax,
    deleteMinAll,
    deleteMaxAll,
    minView,
    maxView,
    minViewWithMultiplicity,
    maxViewWithMultiplicity,

    -- * Splitting
    split,
) where

import Control.Applicative ((<|>))
import qualified Control.Applicative as A
import Control.DeepSeq (NFData (..))
import Control.Monad
import Data.Bifunctor
import Data.Bool
import Data.Coerce
import qualified Data.Foldable as F
import Data.List (genericReplicate)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import qualified Data.Semigroup as SG
import qualified Data.Set as S
import GHC.Natural
import Prelude hiding (concatMap, filter, map, null, traverse)
import qualified Prelude as P

type Tally a = M.Map a Natural

{-# INLINE lift #-}
lift :: (Tally a -> Tally b) -> MultiSet a -> MultiSet b
lift = coerce

{-# INLINE lift2 #-}
lift2 :: (Tally a -> Tally b -> Tally c) -> MultiSet a -> MultiSet b -> MultiSet c
lift2 = coerce

{-# INLINE with2 #-}
with2 :: (Tally a -> Tally b -> c) -> MultiSet a -> MultiSet b -> c
with2 = coerce

{- | A finite multiset of type @a@. Each value has a 'Natural' multiplicity,
  with multiplicity zero indicating that the value is absent.
-}
newtype MultiSet a = MS {unMS :: Tally a} -- invariant: n > 0
    deriving (Eq, NFData)

-- | Via 'union'.
instance (Ord a) => SG.Semigroup (MultiSet a) where
    (<>) = union

instance (Ord a) => Monoid (MultiSet a) where
    mempty = empty
    mappend = (SG.<>)

{- | Orders multisets as their sorted 'toList' expansions would be ordered,
  giving an ordering based on elements rather than the internal representation.
-}
instance (Ord a) => Ord (MultiSet a) where
    compare as bs = compareRuns (M.toAscList $ unMS as) (M.toAscList $ unMS bs)
      where
        compareRuns ((x, n) : xs) ((y, m) : ys) =
            compare x y SG.<> case compare n m of
                EQ -> compareRuns xs ys
                LT -> if P.null xs then LT else GT
                GT -> if P.null ys then GT else LT
        compareRuns xs ys = compare xs ys

instance (Show a) => Show (MultiSet a) where
    showsPrec d ms =
        showParen (d > 10) $
            showString "fromMultiplicityList "
                . shows (M.toAscList $ unMS ms)

instance (Ord a, Read a) => Read (MultiSet a) where
    readsPrec d =
        readParen (d > 10) $ \s -> do
            ("fromMultiplicityList", rest) <- lex s
            (xs, rest') <- reads rest
            pure (fromMultiplicityList xs, rest')

-- | Wrapper providing 'SG.Semigroup' and 'Monoid' using 'maxUnion' rather than 'union'.
newtype MaxUnion a = MaxUnion {getMaxUnion :: MultiSet a}
    deriving (Eq, Ord, Show, Read, NFData)

instance (Ord a) => SG.Semigroup (MaxUnion a) where
    (<>) = coerce maxUnion

instance (Ord a) => Monoid (MaxUnion a) where
    mempty = coerce empty
    mappend = (SG.<>)

-- | The empty 'MultiSet'.
empty :: MultiSet a
empty = MS M.empty

-- | The 'MultiSet' containing the given element with multiplicity 1.
singleton :: a -> MultiSet a
singleton = (`singletonMany` 1)

-- | The 'MultiSet' containing a single element with the given multiplicity.
singletonMany :: a -> Natural -> MultiSet a
singletonMany _ 0 = empty
singletonMany x n = MS $ M.singleton x n

{- | Construct a 'MultiSet' from a list.

For any 'Foldable', use @foldMap 'singleton'@.
-}
fromList :: (Ord a) => [a] -> MultiSet a
fromList = MS . M.fromListWith (+) . P.map (,1)

{- | Construct a 'MultiSet' from a list of pairs of elements and their multiplicity.

For any 'Foldable', use @foldMap (uncurry 'singletonMany')@.
-}
fromMultiplicityList :: (Ord a) => [(a, Natural)] -> MultiSet a
fromMultiplicityList = MS . M.fromListWith (+) . P.filter ((> 0) . snd)

-- | Construct a 'MultiSet' from a 'M.Map' of element multiplicities.
fromMap :: M.Map a Natural -> MultiSet a
fromMap = MS . M.filter (> 0)

-- | Construct a 'MultiSet' from a 'S.Set'.
fromSet :: (Ord a) => S.Set a -> MultiSet a
fromSet = MS . M.fromSet (const 1)

-- | Insert one occurrence of the given element.
insert :: (Ord a) => a -> MultiSet a -> MultiSet a
insert = (`insertMany` 1)

-- | Insert many occurrences of the given element.
insertMany :: (Ord a) => a -> Natural -> MultiSet a -> MultiSet a
insertMany _ 0 = id
insertMany x n = lift $ M.insertWith (+) x n

positive :: Maybe Natural -> Maybe Natural
positive = mfilter (> 0)

(-?) :: Natural -> Natural -> Maybe Natural
x -? y = positive $ x `minusNaturalMaybe` y

-- | Delete one occurrence of the given element.
delete :: (Ord a) => a -> MultiSet a -> MultiSet a
delete = (`deleteMany` 1)

-- | Delete many occurrences of the given element.
deleteMany :: (Ord a) => a -> Natural -> MultiSet a -> MultiSet a
deleteMany _ 0 = id -- not required to maintain invariant
deleteMany x n = lift $ M.update (-? n) x

-- | Delete all occurrences of the given element.
deleteAll :: (Ord a) => a -> MultiSet a -> MultiSet a
deleteAll x = lift $ M.delete x

-- | Delete all elements which don't satisfy the given predicate.
filter :: (a -> Bool) -> MultiSet a -> MultiSet a
filter = filterWithMultiplicity . (const .)

-- | Like 'filter', but the predicate receives the element multiplicity as well.
filterWithMultiplicity :: (a -> Natural -> Bool) -> MultiSet a -> MultiSet a
filterWithMultiplicity = lift . M.filterWithKey

-- | Like 'filter', but the predicate is effectful.
filterA :: (Applicative f) => (a -> f Bool) -> MultiSet a -> f (MultiSet a)
filterA = filterWithMultiplicityA . (const .)

-- | Like 'filterA', but the predicate receives the element multiplicity as well.
filterWithMultiplicityA ::
    (Applicative f) => (a -> Natural -> f Bool) -> MultiSet a -> f (MultiSet a)
filterWithMultiplicityA p =
    fmap MS . M.traverseMaybeWithKey (\x n -> (n <$) . guard <$> p x n) . unMS

{- | Split a `MultiSet` into a pair of `MultiSet`s, the elements of which do
  and do not satisfy the given predicate, respectively.
-}
partition :: (a -> Bool) -> MultiSet a -> (MultiSet a, MultiSet a)
partition = partitionWithMultiplicity . (const .)

-- | Like 'partition', but the predicate is effectful.
partitionA :: (Applicative f) => (a -> f Bool) -> MultiSet a -> f (MultiSet a, MultiSet a)
partitionA = partitionWithMultiplicityA . (const .)

-- | Like 'partition', but the predicate receives the element multiplicity as well.
partitionWithMultiplicity :: (a -> Natural -> Bool) -> MultiSet a -> (MultiSet a, MultiSet a)
partitionWithMultiplicity f = bimap MS MS . M.partitionWithKey f . unMS

-- | Like 'partitionWithMultiplicity', but the predicate is effectful.
partitionWithMultiplicityA ::
    (Applicative f) => (a -> Natural -> f Bool) -> MultiSet a -> f (MultiSet a, MultiSet a)
partitionWithMultiplicityA f =
    fmap (bimap wrap wrap) . M.foldrWithKey classify (pure ([], [])) . unMS
  where
    classify x n = A.liftA2 (\b -> bool second first b ((x, n) :)) (f x n)
    wrap = MS . M.fromDistinctAscList

{- | @'map' f s@ is the 'MultiSet' obtained from applying @f@ to each element
  of @s@. Multiplicities are added when multiple @a@s map to the same @b@,
  and preserved otherwise.
-}
map :: (Ord b) => (a -> b) -> MultiSet a -> MultiSet b
map f = lift $ M.mapKeysWith (+) f

{- | @'mapWithMultiplicity' f s ==
   'fromMultiplicityList' (fmap (uncurry f) ('toMultiplicityList' s))@.

  Multiplicities of equal resulting elements are added, and resulting zero
  multiplicities are discarded.
-}
mapWithMultiplicity :: (Ord b) => (a -> Natural -> (b, Natural)) -> MultiSet a -> MultiSet b
mapWithMultiplicity f = foldlWithMultiplicity' (\ms x n -> uncurry insertMany (f x n) ms) empty

{- | @'mapMultiplicities' f s@ is the 'MultiSet' obtained from applying @f@ to
  the multiplicity of each element of @s@. Zero multiplicities are removed.
-}
mapMultiplicities :: (Natural -> Natural) -> MultiSet a -> MultiSet a
mapMultiplicities f = lift $ M.mapMaybe (positive . Just . f)

-- | Like 'map', but elements can be removed by using 'Nothing' and kept using 'Just'.
mapMaybe :: (Ord b) => (a -> Maybe b) -> MultiSet a -> MultiSet b
mapMaybe f = mapMaybeWithMultiplicity (\x n -> (,n) <$> f x)

{- | Like 'mapWithMultiplicity', but elements can be removed using 'Nothing'
  and kept using 'Just'.
-}
mapMaybeWithMultiplicity ::
    (Ord b) => (a -> Natural -> Maybe (b, Natural)) -> MultiSet a -> MultiSet b
mapMaybeWithMultiplicity f =
    foldlWithMultiplicity' (\ms x n -> maybe ms (flip (uncurry insertMany) ms) (f x n)) empty

-- | @'concatMap' f s@ applies @f@ to each element of @s@, and 'unions' the resulting 'MultiSet's.
concatMap :: (Ord b) => (a -> MultiSet b) -> MultiSet a -> MultiSet b
concatMap f = foldlWithMultiplicity' (\ms x n -> union ms $ mapMultiplicities (* n) $ f x) empty

{- | @'alterMultiplicity' f x s@ sets the multiplicity of @x@ in @s@ to the
result of applying @f@ to its current multiplicity (which might be zero).
-}
alterMultiplicity :: (Ord a) => (Natural -> Natural) -> a -> MultiSet a -> MultiSet a
alterMultiplicity f = lift . M.alter (positive . Just . f . fromMaybe 0)

-- | Like 'alterMultiplicity', but the update function is effectful.
alterMultiplicityF ::
    (Functor f, Ord a) => (Natural -> f Natural) -> a -> MultiSet a -> f (MultiSet a)
alterMultiplicityF f x = fmap MS . M.alterF (fmap (positive . Just) . f . fromMaybe 0) x . unMS

-- | @'setMultiplicity' x n s@ sets the multiplicity of @x@ in @s@ to @n@.
setMultiplicity :: (Ord a) => a -> Natural -> MultiSet a -> MultiSet a
setMultiplicity x n = alterMultiplicity (const n) x

{- | Right-associatively fold the elements and their multiplicities of a
  'MultiSet' into a single value.
-}
foldrWithMultiplicity :: (a -> Natural -> r -> r) -> r -> MultiSet a -> r
foldrWithMultiplicity f r = M.foldrWithKey f r . unMS

-- | Strict version of 'foldrWithMultiplicity'.
foldrWithMultiplicity' :: (a -> Natural -> r -> r) -> r -> MultiSet a -> r
foldrWithMultiplicity' f r = M.foldrWithKey' f r . unMS

{- | Left-associatively fold the elements and their multiplicities of a
  'MultiSet' into a single value.
-}
foldlWithMultiplicity :: (r -> a -> Natural -> r) -> r -> MultiSet a -> r
foldlWithMultiplicity f r = M.foldlWithKey f r . unMS

-- | Strict version of 'foldlWithMultiplicity'.
foldlWithMultiplicity' :: (r -> a -> Natural -> r) -> r -> MultiSet a -> r
foldlWithMultiplicity' f r = M.foldlWithKey' f r . unMS

-- | Fold the elements and their multiplicities of a 'MultiSet' using the given 'Monoid'.
foldMapWithMultiplicity :: (Monoid m) => (a -> Natural -> m) -> MultiSet a -> m
foldMapWithMultiplicity f = M.foldMapWithKey f . unMS

-- | Like 'traverseWithMultiplicity', but preserves element multiplicities.
traverse :: (Applicative f, Ord b) => (a -> f b) -> MultiSet a -> f (MultiSet b)
traverse f = traverseWithMultiplicity (\x n -> (,n) <$> f x)

{- | Like 'traverse', but elements can be removed using 'Nothing' and kept
using 'Just'.
-}
traverseMaybe :: (Applicative f, Ord b) => (a -> f (Maybe b)) -> MultiSet a -> f (MultiSet b)
traverseMaybe f = traverseMaybeWithMultiplicity (\x n -> fmap (,n) <$> f x)

{- | @'traverseWithMultiplicity' f s@ applies the effect @f@ to each distinct
element of @s@ and its multiplicity in increasing order of elements. The
@(element, multiplicity)@ result pairs are used to construct the resulting
'MultiSet'.
-}
traverseWithMultiplicity ::
    (Applicative f, Ord b) => (a -> Natural -> f (b, Natural)) -> MultiSet a -> f (MultiSet b)
traverseWithMultiplicity f = traverseMaybeWithMultiplicity ((fmap Just .) . f)

{- | Like 'traverseWithMultiplicity', but elements can be removed using
'Nothing' and kept using 'Just'.
-}
traverseMaybeWithMultiplicity ::
    (Applicative f, Ord b) =>
    (a -> Natural -> f (Maybe (b, Natural))) ->
    MultiSet a ->
    f (MultiSet b)
traverseMaybeWithMultiplicity f =
    foldrWithMultiplicity (\x n -> A.liftA2 (maybe id (uncurry insertMany)) (f x n)) (pure empty)

{- | Like 'traverseWithMultiplicity', but discards the results of the effect
and doesn't build a resulting 'MultiSet'.
-}
traverseWithMultiplicity_ :: (Applicative f) => (a -> Natural -> f b) -> MultiSet a -> f ()
traverseWithMultiplicity_ f = foldrWithMultiplicity (\x n rest -> f x n *> rest) (pure ())

-- | Convert to a 'M.Map' from element to its multiplicity.
toMap :: MultiSet a -> M.Map a Natural
toMap = unMS

-- | Convert to a list of @(element, multiplicity)@ pairs.
toMultiplicityList :: MultiSet a -> [(a, Natural)]
toMultiplicityList = M.toAscList . unMS

-- | The set of distinct elements.
toSet :: MultiSet a -> S.Set a
toSet = M.keysSet . unMS

-- | The list of distinct elements.
toDistinctList :: MultiSet a -> [a]
toDistinctList = M.keys . unMS

-- | Convert to an ascending list, repeating each element according to its multiplicity.
toList :: MultiSet a -> [a]
toList = foldrWithMultiplicity (\x n -> (++) $ genericReplicate n x) []

-- | Is the 'MultiSet' empty?
null :: MultiSet a -> Bool
null = M.null . unMS

-- | Is the value a member of the 'MultiSet'?
member :: (Ord a) => a -> MultiSet a -> Bool
member x = M.member x . unMS

-- | Is the value not a member of the 'MultiSet'?
notMember :: (Ord a) => a -> MultiSet a -> Bool
notMember x = M.notMember x . unMS

-- | How many times is the element contained in the 'MultiSet'?
multiplicity :: (Ord a) => a -> MultiSet a -> Natural
multiplicity x = M.findWithDefault 0 x . unMS

-- | How many elements are in the 'MultiSet'? This is the sum of multiplicities.
size :: MultiSet a -> Natural
size = M.foldl' (+) 0 . unMS

-- | How many distinct elements are in the 'MultiSet'?
distinctSize :: MultiSet a -> Int
distinctSize = M.size . unMS

-- | The union two 'MultiSet's, adding multiplicities for elements present in both.
union :: (Ord a) => MultiSet a -> MultiSet a -> MultiSet a
union = lift2 $ M.unionWith (+)

{- | The union of a list of 'MultiSet's.

For any 'Foldable', use @foldMap id@.
-}
unions :: (Ord a) => [MultiSet a] -> MultiSet a
unions = MS . M.unionsWith (+) . P.map unMS

-- | The difference of two 'MultiSet's.
difference :: (Ord a) => MultiSet a -> MultiSet a -> MultiSet a
difference = lift2 $ M.differenceWith (-?)

{- | The symmetric difference of two 'MultiSet's, taking the absolute difference
  of multiplicities for elements present in both.
-}
symmetricDifference :: (Ord a) => MultiSet a -> MultiSet a -> MultiSet a
symmetricDifference = lift2 $ M.mergeWithKey (\_ m n -> m -? n <|> n -? m) id id

-- | The intersection of two 'MultiSet's.
intersection :: (Ord a) => MultiSet a -> MultiSet a -> MultiSet a
intersection = lift2 $ M.intersectionWith min

-- | The intersection of a series of 'MultiSet's.
intersections :: (Ord a) => NonEmpty (MultiSet a) -> MultiSet a
intersections (x :| xs) = F.foldl' intersection x xs

-- | The union of two 'MultiSet's, taking the maximum multiplicity of each element.
maxUnion :: (Ord a) => MultiSet a -> MultiSet a -> MultiSet a
maxUnion = lift2 $ M.unionWith max

{- | The cartesian product of two 'MultiSet's. Each pair @(x, y)@ appears with
  multiplicity equal to the multiplicity of @x@ in the first 'MultiSet'
  multiplied by the multiplicity of @y@ in the second.
-}
cartesianProduct :: (Ord a, Ord b) => MultiSet a -> MultiSet b -> MultiSet (a, b)
cartesianProduct xs = concatMap (\y -> map (,y) xs)

-- | Is the first 'MultiSet' contained in the second, respecting multiplicities?
isSubsetOf :: (Ord a) => MultiSet a -> MultiSet a -> Bool
isSubsetOf = with2 $ M.isSubmapOfBy (<=)

-- | Like 'isSubsetOf', but 'False' if the two 'MultiSet's are equal.
isProperSubsetOf :: (Ord a) => MultiSet a -> MultiSet a -> Bool
isProperSubsetOf x y = x /= y && isSubsetOf x y

-- | Do the two 'MultiSet's have no common elements?
disjoint :: (Ord a) => MultiSet a -> MultiSet a -> Bool
disjoint = with2 M.disjoint

{- | Find the largest element smaller than the given one and return the
  corresponding @(element, multiplicity)@ pair.
-}
lookupLT :: (Ord a) => a -> MultiSet a -> Maybe (a, Natural)
lookupLT x = M.lookupLT x . unMS

{- | Find the largest element smaller than or equal to the given one and return
  the corresponding @(element, multiplicity)@ pair.
-}
lookupLE :: (Ord a) => a -> MultiSet a -> Maybe (a, Natural)
lookupLE x = M.lookupLE x . unMS

{- | Find the smallest element greater than the given one and return the
  corresponding @(element, multiplicity)@ pair.
-}
lookupGT :: (Ord a) => a -> MultiSet a -> Maybe (a, Natural)
lookupGT x = M.lookupGT x . unMS

{- | Find the smallest element greater than or equal to the given one and
  return the corresponding @(element, multiplicity)@ pair.
-}
lookupGE :: (Ord a) => a -> MultiSet a -> Maybe (a, Natural)
lookupGE x = M.lookupGE x . unMS

-- | Find the smallest element of the 'MultiSet' and its multiplicity.
lookupMin :: MultiSet a -> Maybe (a, Natural)
lookupMin = M.lookupMin . unMS

-- | Find the largest element of the 'MultiSet' and its multiplicity.
lookupMax :: MultiSet a -> Maybe (a, Natural)
lookupMax = M.lookupMax . unMS

-- | Remove one occurrence of the smallest element of the 'MultiSet'.
deleteMin :: MultiSet a -> MultiSet a
deleteMin = lift $ M.updateMin (-? 1)

-- | Remove one occurrence of the largest element of the 'MultiSet'.
deleteMax :: MultiSet a -> MultiSet a
deleteMax = lift $ M.updateMax (-? 1)

-- | Remove all occurrences of the smallest element of the 'MultiSet'.
deleteMinAll :: MultiSet a -> MultiSet a
deleteMinAll = lift M.deleteMin

-- | Remove all occurrences of the largest element of the 'MultiSet'.
deleteMaxAll :: MultiSet a -> MultiSet a
deleteMaxAll = lift M.deleteMax

-- two traversals for both of these, but maybe we don't care for now?

{- | Return the least element and the remaining 'MultiSet' with one occurrence
  removed, or 'Nothing' if empty.
-}
minView :: (Ord a) => MultiSet a -> Maybe (a, MultiSet a)
minView ms = do
    ((x, n), xs) <- M.minViewWithKey $ unMS ms
    pure (x, insertMany x (n - 1) $ MS xs)

{- | Return the greatest element and the remaining 'MultiSet' with one
  occurrence removed, or 'Nothing' if empty.
-}
maxView :: (Ord a) => MultiSet a -> Maybe (a, MultiSet a)
maxView ms = do
    ((x, n), xs) <- M.maxViewWithKey $ unMS ms
    pure (x, insertMany x (n - 1) $ MS xs)

{- | Return the least element with its multiplicity, and the remaining
  'MultiSet', or 'Nothing' if empty.
-}
minViewWithMultiplicity :: MultiSet a -> Maybe ((a, Natural), MultiSet a)
minViewWithMultiplicity = fmap (second MS) . M.minViewWithKey . unMS

{- | Return the greatest element with its multiplicity, and the remaining
  'MultiSet', or 'Nothing' if empty.
-}
maxViewWithMultiplicity :: MultiSet a -> Maybe ((a, Natural), MultiSet a)
maxViewWithMultiplicity = fmap (second MS) . M.maxViewWithKey . unMS

{- | @'split' x s@ produces the tuple @(sl, nx, sg)@, where @nx@ is the
  multiplicity of @x@ in @s@, and @sl@ and @sg@ are 'MultiSet's containing
  the elements of @s@ which are less than and greater than @x@, respectively.

  @x@ is not required to be a 'member' of @s@, and @nx@ will be zero if it
  isn't.
-}
split :: (Ord a) => a -> MultiSet a -> (MultiSet a, Natural, MultiSet a)
split x = ret . M.splitLookup x . unMS
  where
    ret (ls, m, rs) = (MS ls, fromMaybe 0 m, MS rs)

-- TODO:
-- - mapMonotonic and other unsafe functions?
-- - Intersection wrapper with Semigroup instance?