packages feed

horizontal-rule-0.7.0.0: test/Test/Predicates/Internal/FlowMatcher.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | An implementation of bipartite matching using the Ford-Fulkerson algorithm.
module Test.Predicates.Internal.FlowMatcher where

import Control.Monad (forM_, when)
import Control.Monad.ST (ST)
import Data.Array.IArray (Array, assocs, elems)
import Data.Array.ST
  ( MArray (newArray),
    STArray,
    newListArray,
    readArray,
    runSTArray,
    writeArray,
  )
import Data.List ((\\))
import Data.Maybe (catMaybes)

-- $setup
-- >>> :set -Wno-type-defaults

-- | Computes the best bipartite matching of the elements in the two lists,
-- given the compatibility function.
--
-- Returns matched pairs, then unmatched lhs elements, then unmatched rhs
-- elements.
--
-- >>> bipartiteMatching (==) [1 .. 5] [6, 5 .. 2]
-- ([(2,2),(3,3),(4,4),(5,5)],[1],[6])
bipartiteMatching ::
  forall a b. (a -> b -> Bool) -> [a] -> [b] -> ([(a, b)], [a], [b])
bipartiteMatching compatible xs ys = (matchedPairs, unmatchedX, unmatchedY)
  where
    matchedPairs :: [(a, b)]
    matchedPairs = [(xs !! i, ys !! j) | (i, Just j) <- assocs matches]

    unmatchedX :: [a]
    unmatchedX = [xs !! i | (i, Nothing) <- assocs matches]

    unmatchedY :: [b]
    unmatchedY = [ys !! j | j <- [0 .. numYs - 1] \\ catMaybes (elems matches)]

    matches :: Array Int (Maybe Int)
    matches = runSTArray st

    st :: forall s. ST s (STArray s Int (Maybe Int))
    st = do
      compatArray <-
        newListArray
          ((0, 0), (numXs - 1, numYs - 1))
          [compatible x y | x <- xs, y <- ys] ::
          ST s (STArray s (Int, Int) Bool)
      matchArray <-
        newArray (0, numXs - 1) Nothing ::
          ST s (STArray s Int (Maybe Int))
      forM_ [0 .. numYs - 1] $ \j -> do
        seen <-
          newArray (0, numXs - 1) False :: ST s (STArray s Int Bool)
        _ <- go compatArray j matchArray seen
        return ()

      return matchArray

    numXs, numYs :: Int
    numXs = length xs
    numYs = length ys

    go ::
      forall s.
      STArray s (Int, Int) Bool ->
      Int ->
      STArray s Int (Maybe Int) ->
      STArray s Int Bool ->
      ST s Bool
    go compatArray j matchArray seen = loop False 0
      where
        loop True _ = return True
        loop _ i
          | i == numXs = return False
          | otherwise = do
            compat <- readArray compatArray (i, j)
            isSeen <- readArray seen i
            replace <-
              if isSeen || not compat
                then return False
                else do
                  writeArray seen i True
                  matchNum <- readArray matchArray i
                  case matchNum of
                    Nothing -> return True
                    Just n -> go compatArray n matchArray seen
            when replace $ writeArray matchArray i (Just j)
            loop replace (i + 1)