packages feed

toysolver-0.7.0: src/ToySolver/Converter/PB/Internal/LargestIntersectionFinder.hs

{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_HADDOCK show-extensions #-}
{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  ToySolver.Converter.PB.Internal.LargestIntersectionFinder
-- Copyright   :  (c) Masahiro Sakai 2018
-- License     :  BSD-style
--
-- Maintainer  :  masahiro.sakai@gmail.com
-- Stability   :  provisional
-- Portability :  non-portable
--
-----------------------------------------------------------------------------
module ToySolver.Converter.PB.Internal.LargestIntersectionFinder
  ( Table
  , empty
  , fromSet
  , fromList
  , toSet
  , toList
  , insert
  , findLargestIntersectionSet
  ) where

import Data.IntMap (IntMap)
import qualified Data.IntMap.Strict as IntMap
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import Data.List hiding (insert)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid
#endif
import Data.Ord
import Data.Set (Set)
import qualified Data.Set as Set

data Table
  = Table
  { numSets   :: !Int
  , toSetId   :: Map IntSet SetId
  , fromSetId :: IntMap IntSet
  , invMember :: IntMap (IntMap Count) -- e ↦ {s ↦ 1 | e∈s}
  }
  deriving (Show)

type SetId = Int
type Count = Int

empty :: Table
empty =
  Table
  { numSets   = 0
  , toSetId   = Map.empty
  , fromSetId = IntMap.empty
  , invMember = IntMap.empty
  }

fromList :: [IntSet] -> Table
fromList = fromSet . Set.fromList

fromSet :: Set IntSet -> Table
fromSet ss =
  Table
  { numSets   = Set.size ss
  , toSetId   = Map.fromList [(s,i) | (i,s) <- l]
  , fromSetId = IntMap.fromList l
  , invMember =
      IntMap.unionsWith IntMap.union
        [ IntMap.fromAscList [(e, IntMap.singleton i 1) | e <- IntSet.toAscList s]
        | (i,s) <- l
        ]
  }
  where
    l = zip [0..] (Set.toList ss)

toSet :: Table -> Set IntSet
toSet = Map.keysSet . toSetId

toList :: Table -> [IntSet]
toList = Set.toList . toSet

insert :: IntSet -> Table -> Table
insert s t
  | s `Map.member` toSetId t = t
  | otherwise =
      t
      { numSets = n + 1
      , toSetId = Map.insert s n (toSetId t)
      , fromSetId = IntMap.insert n s (fromSetId t)
      , invMember =
          IntMap.unionWith IntMap.union
            (IntMap.fromAscList [(e, IntMap.singleton n 1) | e <- IntSet.toAscList s])
            (invMember t)
      }
  where
    n = numSets t

-- | Given a set S and a family of sets U, find a T∈S such that S∩T has maximum cardinality.
-- In case of tie, smaller T is preferred.
findLargestIntersectionSet :: IntSet -> Table -> Maybe IntSet
findLargestIntersectionSet s t
  | IntMap.null m =
      if IntSet.empty `Map.member` toSetId t
      then Just IntSet.empty
      else Nothing
  | otherwise = Just $! fromSetId t IntMap.! n
  where
    m :: IntMap Count
    m = IntMap.unionsWith (+) [IntMap.findWithDefault IntMap.empty e (invMember t) | e <- IntSet.toList s]
    (n,_,_) = maximumBy (comparing (\(_,c,_) -> c) <> flip (comparing (\(_,_,size) -> size))) $
                [(i, c, IntSet.size (fromSetId t IntMap.! i)) | (i,c) <- IntMap.toList m]