packages feed

ac-library-hs 1.5.2.1 → 1.5.3.0

raw patch · 9 files changed

+949/−14 lines, 9 filesdep ~randomdep ~unordered-containersPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: random, unordered-containers

API changes (from Hackage documentation)

+ AtCoder.Extra.AhoCorasick: AhoCorasick :: {-# UNPACK #-} !Int -> !Vector (HashMap Int Int) -> !Vector Int -> !Vector Int -> !Vector Int -> !Vector Int -> AhoCorasick
+ AtCoder.Extra.AhoCorasick: [outputAc] :: AhoCorasick -> !Vector Int
+ AtCoder.Extra.AhoCorasick: [parentAc] :: AhoCorasick -> !Vector Int
+ AtCoder.Extra.AhoCorasick: [patternAc] :: AhoCorasick -> !Vector Int
+ AtCoder.Extra.AhoCorasick: [sizeAc] :: AhoCorasick -> {-# UNPACK #-} !Int
+ AtCoder.Extra.AhoCorasick: [suffixAc] :: AhoCorasick -> !Vector Int
+ AtCoder.Extra.AhoCorasick: [trieAc] :: AhoCorasick -> !Vector (HashMap Int Int)
+ AtCoder.Extra.AhoCorasick: build :: HasCallStack => Vector (Vector Int) -> AhoCorasick
+ AtCoder.Extra.AhoCorasick: data AhoCorasick
+ AtCoder.Extra.AhoCorasick: match :: HasCallStack => AhoCorasick -> Vector Int -> Vector (Int, Int)
+ AtCoder.Extra.AhoCorasick: next :: HasCallStack => AhoCorasick -> Int -> Int -> Int
+ AtCoder.Extra.AhoCorasick: nextN :: HasCallStack => AhoCorasick -> Int -> Vector Int -> Int
+ AtCoder.Extra.AhoCorasick: size :: HasCallStack => AhoCorasick -> Int
+ AtCoder.Extra.DsuMonoid: build :: (PrimMonad m, Semigroup a, Unbox a) => Vector a -> m (DsuMonoid (PrimState m) a)
+ AtCoder.Extra.DsuMonoid: data DsuMonoid s a
+ AtCoder.Extra.DsuMonoid: groups :: PrimMonad m => DsuMonoid (PrimState m) a -> m (Vector (Vector Int))
+ AtCoder.Extra.DsuMonoid: leader :: (HasCallStack, PrimMonad m) => DsuMonoid (PrimState m) a -> Int -> m Int
+ AtCoder.Extra.DsuMonoid: merge :: (HasCallStack, PrimMonad m, Semigroup a, Unbox a) => DsuMonoid (PrimState m) a -> Int -> Int -> m Int
+ AtCoder.Extra.DsuMonoid: mergeMaybe :: (HasCallStack, PrimMonad m, Semigroup a, Unbox a) => DsuMonoid (PrimState m) a -> Int -> Int -> m (Maybe Int)
+ AtCoder.Extra.DsuMonoid: merge_ :: (PrimMonad m, Semigroup a, Unbox a) => DsuMonoid (PrimState m) a -> Int -> Int -> m ()
+ AtCoder.Extra.DsuMonoid: new :: (PrimMonad m, Monoid a, Unbox a) => Int -> m (DsuMonoid (PrimState m) a)
+ AtCoder.Extra.DsuMonoid: read :: (PrimMonad m, Unbox a) => DsuMonoid (PrimState m) a -> Int -> m a
+ AtCoder.Extra.DsuMonoid: same :: (HasCallStack, PrimMonad m) => DsuMonoid (PrimState m) a -> Int -> Int -> m Bool
+ AtCoder.Extra.DsuMonoid: size :: (HasCallStack, PrimMonad m) => DsuMonoid (PrimState m) a -> Int -> m Int
+ AtCoder.Extra.DsuMonoid: unsafeRead :: (PrimMonad m, Unbox a) => DsuMonoid (PrimState m) a -> Int -> m a
+ AtCoder.Extra.DsuMonoid: unsafeWrite :: (PrimMonad m, Unbox a) => DsuMonoid (PrimState m) a -> Int -> a -> m ()

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for acl-hs +## 1.5.3.0 -- June 2025++- Added `AtCoder.Extra.AhoCorasick`+- Added `AtCoder.Extra.DsuMonoid`+- Fixed the implementation of `KdTree.findNearstPoint`+ ## 1.5.2.0 -- June 2025  - Added `AtCoder.Extra.Vector.Prim`
ac-library-hs.cabal view
@@ -4,7 +4,7 @@ -- PVP summary:  +-+------- breaking API changes --               | | +----- non-breaking API additions --               | | | +--- code changes with no API change-version:         1.5.2.1+version:         1.5.3.0 synopsis:        Data structures and algorithms description:   Haskell port of [ac-library](https://github.com/atcoder/ac-library), a library for competitive@@ -35,15 +35,16 @@  common dependencies   build-depends:-    , base               >=4.9     && <4.22-    , bitvec             <1.2-    , bytestring         <0.14-    , primitive          >=0.6.4.0 && <0.10-    , random             >=1.2.0   && <1.3-    , transformers       >=0.2.0.0-    , vector             >=0.13.0  && <0.14-    , vector-algorithms  <0.10-    , wide-word          <0.2+    , base                  >=4.9     && <4.22+    , bitvec                <1.2+    , bytestring            <0.14+    , primitive             >=0.6.4.0 && <0.10+    , random                >=1.2.0   && <1.4+    , transformers          >=0.2.0.0+    , unordered-containers  <0.3+    , vector                >=0.13.0  && <0.14+    , vector-algorithms     <0.10+    , wide-word             <0.2    default-language: GHC2021 @@ -53,7 +54,9 @@   exposed-modules:     AtCoder.Convolution     AtCoder.Dsu+    AtCoder.Extra.AhoCorasick     AtCoder.Extra.Bisect+    AtCoder.Extra.DsuMonoid     AtCoder.Extra.DynLazySegTree     AtCoder.Extra.DynLazySegTree.Persistent     AtCoder.Extra.DynLazySegTree.Raw@@ -174,6 +177,8 @@     Tests.Extra.Seq.Map     Tests.Extra.Tree     Tests.Extra.Tree.Lct+    Tests.Extra.Vector+    Tests.Extra.Vector.Prim     Tests.Extra.WaveletMatrix     Tests.Extra.WaveletMatrix.BitVector     Tests.Extra.WaveletMatrix.Raw@@ -230,6 +235,7 @@     Bench.Matrix     Bench.ModInt     Bench.Montgomery64+    Bench.MulMod     Bench.PowMod     Bench.RepeatWithIndex     Bench.RepeatWithoutIndex
+ benchmarks/Bench/MulMod.hs view
@@ -0,0 +1,64 @@+module Bench.MulMod (benches) where++import BenchLib.MulMod.Barrett64 qualified as Barrett64+import BenchLib.MulMod.BarrettWideWord qualified as BarrettWideWord+import BenchLib.MulMod.Montgomery qualified as Montgomery+import Criterion+import Data.Bifunctor (first)+import Data.Vector.Unboxed qualified as VU+import Data.Word (Word32, Word64)+import System.Random++n :: Int+n = 10000++btWW :: BarrettWideWord.Barrett+btWW = BarrettWideWord.new64 998244353++bt64 :: Barrett64.Barrett+bt64 = Barrett64.new 998244353++mont :: Montgomery.Montgomery+mont = Montgomery.new 998244353++-- | This benchmark is almost nonsense, as the pre calculation' overhead is not considered while the+-- real use case is @powMod@. However, it's useful when optimizing the successive calculation.+benches32 :: Benchmark+benches32 =+  bgroup+    "mulMod Word32 vector"+    [ bench "barrettWideWord" $ whnf (VU.foldl' (\acc -> BarrettWideWord.mulMod btWW acc . fromIntegral) w64) nonZeroRandomVec32,+      bench "barrett" $ whnf (VU.foldl' (\acc -> Barrett64.mulMod bt64 acc . fromIntegral) w64) nonZeroRandomVec32,+      -- NOTE: It skips the last reduce.+      bench "montgomery" $ whnf (VU.foldl' (\acc -> Montgomery.mulModGenerated mont acc . Montgomery.generate mont . fromIntegral) w64) nonZeroRandomVec32,+      bench "mod" $ whnf (VU.foldl' (\acc n -> acc * n `mod` 998244353) w32) nonZeroRandomVec32,+      bench "rem" $ whnf (VU.foldl' (\acc n -> acc * n `rem` 998244353) w32) nonZeroRandomVec32+    ]+  where+    w32 :: Word32+    w32 = 1+    w64 :: Word64+    w64 = 1+    -- [1, 998244383)+    nonZeroRandomVec32 :: VU.Vector Word32+    nonZeroRandomVec32 = VU.map fromIntegral $ VU.unfoldrExactN n (first (+ 1) . genWord64R (998244383 - 2)) (mkStdGen 123456789)++-- | This benchmark is almost nonsense, as the pre calculation' overhead is not considered while the+-- real use case is @powMod@. However, it's useful when optimizing the successive calculation.+benches :: Benchmark+benches =+  bgroup+    "mulMod Word64 vector"+    [ bench "barrettWideWord" $ whnf (VU.foldl' (BarrettWideWord.mulMod btWW) w64) nonZeroRandomVec64,+      bench "barrett" $ whnf (VU.foldl' (\acc -> Barrett64.mulMod bt64 acc) w64) nonZeroRandomVec64,+      -- NOTE: It skips the last reduce.+      bench "montgomery" $ whnf (VU.foldl' (\acc -> Montgomery.mulModGenerated mont acc . Montgomery.generate mont) w64) nonZeroRandomVec64,+      bench "mod" $ whnf (VU.foldl' (\acc n -> acc * n `mod` 998244353) w64) nonZeroRandomVec64,+      bench "rem" $ whnf (VU.foldl' (\acc n -> acc * n `rem` 998244353) w64) nonZeroRandomVec64+    ]+  where+    w64 :: Word64+    w64 = 1+    -- [1, 998244383)+    nonZeroRandomVec64 :: VU.Vector Word64+    nonZeroRandomVec64 = VU.map fromIntegral $ VU.unfoldrExactN n (first (+ 1) . genWord64R (998244383 - 2)) (mkStdGen 123456789)
+ src/AtCoder/Extra/AhoCorasick.hs view
@@ -0,0 +1,284 @@+-- | Aho–Corasick algorithm is a fast dictionary-matching (multi-pattern matching) algorithm.+--+-- ==== __Example__+--+-- >>> import AtCoder.Extra.AhoCorasick qualified as AC+-- >>> import Data.Vector.Unboxed qualified as VU+--+-- Pattern strings must be given as @V.Vector (VU.Vector Int)@:+--+-- >>> let patterns = V.fromList [VU.fromList [0, 1], VU.fromList [0, 2], VU.fromList [2, 3, 4]]+-- >>> let ac = AC.build patterns+-- >>> AC.size ac+-- 7+--+-- The automaton could be run manually with `next` or `nextN`:+--+-- >>> AC.nextN ac {- empty node -} 0 (VU.fromList [0, 2, 3])+-- 5+--+-- `match` returns a vector of @(endPos, patternId)@:+--+-- >>> --                         [.....) pattern 0+-- >>> --                               [.......) pattern2+-- >>> AC.match ac $ VU.fromList [0, 1, 2, 3, 4]+-- [(2,0),(5,2)]+--+-- If you need a vector of @(startPos, patternId)@, you must manually map the result:+--+-- >>> let f (!end, !patId) = (end - VU.length (patterns V.! patId), patId)+-- >>> --                                    [.....) pattern 0+-- >>> --                                          [.......) pattern2+-- >>> VU.map f . AC.match ac $ VU.fromList [0, 1, 2, 3, 4]+-- [(0,0),(2,2)]+--+-- Note that duplicate patterns are only counted once with `match`.+--+-- @since 1.5.3.0+module AtCoder.Extra.AhoCorasick+  ( AhoCorasick (..),+    build,+    size,+    next,+    nextN,+    match,+  )+where++-- TODO: Generalize with Hash + Unbox? Int-only implementation is faster though.++import AtCoder.Extra.Vector qualified as EV+import AtCoder.Internal.Queue qualified as Q+import Control.Monad (when)+import Control.Monad.Fix (fix)+import Control.Monad.ST (runST)+import Data.Foldable (for_)+import Data.HashMap.Strict qualified as HM+import Data.Vector qualified as V+import Data.Vector.Generic qualified as VG+import Data.Vector.Generic.Mutable qualified as VGM+import Data.Vector.Mutable qualified as VM+import Data.Vector.Unboxed qualified as VU+import Data.Vector.Unboxed.Mutable qualified as VUM+import GHC.Stack (HasCallStack)++-- | Aho–Corasick algorithm data.+--+-- @since 1.5.3.0+data AhoCorasick = AhoCorasick+  { -- | The number of nodes in the trie.+    --+    -- @since 1.5.3.0+    sizeAc :: {-# UNPACK #-} !Int,+    -- | A trie (-like directed graph) of input words: Vertex -> (Char -> Vertex).+    --+    -- @since 1.5.3.0+    trieAc :: !(V.Vector (HM.HashMap Int Int)),+    -- | Node data of links to parent vertex.+    --+    -- @since 1.5.3.0+    parentAc :: !(VU.Vector Int),+    -- | Node data that represents completed pattern string or nothing (@-1@).+    --+    -- @since 1.5.3.0+    patternAc :: !(VU.Vector Int),+    -- | Node data of links to the longest suffix vertex.+    --+    -- @since 1.5.3.0+    suffixAc :: !(VU.Vector Int),+    -- | Node data of links to the longest suffix pattern vertex.+    --+    -- @since 1.5.3.0+    outputAc :: !(VU.Vector Int)+  }++-- | \(O(\sum_i |S_i|)\)+--+-- ==== Constraints+-- - \(|S_i| > 0\)+--+-- @since 1.5.3.0+{-# INLINEABLE build #-}+build ::+  (HasCallStack) =>+  -- | Pattern strings.+  V.Vector (VU.Vector Int) ->+  -- | Aho–Corasick automaton based on a trie.+  AhoCorasick+build patterns+  | VG.null patterns =+      -- root only+      AhoCorasick+        1+        (V.singleton HM.empty)+        (VU.replicate 1 (-1))+        (VU.replicate 1 0)+        (VU.replicate 1 0)+        (VU.replicate 1 0)+  | otherwise =+      let (!nNodes, !patternMap, !trie, !parent) = buildTrie patterns+          (!suffix, !output) = runBfs nNodes trie patternMap+       in AhoCorasick nNodes trie parent patternMap suffix output++-- | \(O(1)\) Returns the number of nodes in the trie.+--+-- @since 1.5.3.0+{-# INLINE size #-}+size :: (HasCallStack) => AhoCorasick -> Int+size = sizeAc++-- | \(O(1)\) Retrieves the next node to visit.+--+-- @since 1.5.3.0+{-# INLINEABLE next #-}+-- TODO: benchmark INLINE+next ::+  (HasCallStack) =>+  -- | The automaton.+  AhoCorasick ->+  -- | Current node ID (empty node is @0@).+  Int ->+  -- | Character.+  Int ->+  -- | Next node ID.+  Int+next AhoCorasick {trieAc, suffixAc} v0 c = inner v0+  where+    inner v = case HM.lookup c (trieAc VG.! v) of+      Just end -> end+      Nothing+        -- no hope+        | v == 0 -> 0+        -- fallback to the longest match suffix+        | otherwise -> inner $! suffixAc VG.! v++-- | \(n\) Applies `next` N times for a given input string.+--+-- ==== Constraints+--+-- @since 1.5.3.0+{-# INLINE nextN #-}+nextN ::+  (HasCallStack) =>+  -- | The automaton.+  AhoCorasick ->+  -- | Current node.+  Int ->+  -- | String.+  VU.Vector Int ->+  -- | Resulting node.+  Int+nextN ac = VU.foldl' (next ac)++-- | \(O(|T|)\) Runs dictionary matching (multi-pattern matching) in linear time and returns a list+-- of @(endPos, patId)@, where @[endPos - patLen, endPos)@ corresponds to the interval of original+-- source slice.+--+-- Note that duplicate patterns are counted just once with one of them; if pattern A and B are the+-- same, their appearence is counted as either A or B.+--+-- @since 1.5.3.0+{-# INLINEABLE match #-}+match :: (HasCallStack) => AhoCorasick -> VU.Vector Int -> VU.Vector (Int, Int)+match ac@AhoCorasick {patternAc, outputAc} =+  EV.iconcatMap (\i v -> VU.unfoldr (f i) v) . VU.postscanl' (next ac) 0+  where+    f :: Int -> Int -> Maybe ((Int, Int), Int)+    f _ 0 = Nothing+    f i v = case patternAc VG.! v of+      -- NOTE: Do not perform early return, as the initial vertex can be non-pattern+      -1 -> f i (outputAc VG.! v)+      -- NOTE: Here we use `i + 1`, where [pos - patLen, pos) makes up a half-open interval.+      pat -> Just ((i + 1, pat), outputAc VG.! v)++-- | \(O(\sum_i |S_i| \Gamma)\)+{-# INLINEABLE buildTrie #-}+buildTrie :: (HasCallStack) => V.Vector (VU.Vector Int) -> (Int, VU.Vector Int, V.Vector (HM.HashMap Int Int), VU.Vector Int)+buildTrie patternStrings = runST $ do+  let !nMaxNodes = (1 +) . V.sum $ V.map VG.length patternStrings++  -- allocator+  nNodesVec <- VUM.replicate 1 (1 :: Int)++  -- components+  nextVec <- VM.replicate nMaxNodes HM.empty+  parentVec <- VUM.replicate nMaxNodes (0 :: Int)++  -- create a trie and collect pattern vertices+  patternVerts <-+    (VU.convert <$>) . V.forM patternStrings $+      VG.foldM'+        ( \ !u c -> do+            v0 <- HM.lookup c <$> VGM.read nextVec u+            case v0 of+              Nothing -> do+                -- allocate a new vertex index+                v <- VGM.read nNodesVec 0+                VGM.write nNodesVec 0 $! v + 1+                -- store the next vertex link+                VGM.modify nextVec (HM.insert c v) u+                -- fill the vertex information+                VGM.write parentVec v u+                pure v+              Just v -> do+                pure v+        )+        0++  !nNodes <- VGM.read nNodesVec 0++  let !pattern = VU.create $ do+        -- We could replace the following with VU.accumulate+        patVec <- VUM.replicate nNodes (-1 :: Int)+        VU.iforM_ patternVerts $ \iPattern v -> do+          VGM.write patVec v iPattern+        pure patVec+  !trie <- VG.take nNodes <$> V.unsafeFreeze nextVec+  !parent <- VG.take nNodes <$> VU.unsafeFreeze parentVec+  pure (nNodes, pattern, trie, parent)++-- | \(O(\sum_i |S_i| \Gamma)\)+{-# INLINEABLE runBfs #-}+runBfs :: (HasCallStack) => Int -> V.Vector (HM.HashMap Int Int) -> VU.Vector Int -> (VU.Vector Int, VU.Vector Int)+runBfs nNodes trie patternMap = runST $ do+  suffixVec <- VUM.replicate nNodes (0 :: Int)+  outputVec <- VUM.replicate nNodes (0 :: Int)++  que <- Q.new @_ @Int nNodes+  for_ (HM.elems (trie VG.! 0)) $ \v -> do+    Q.pushBack que v++  -- TODO: deduplicate with `next`+  let nextM c u = case HM.lookup c (trie VG.! u) of+        Just end -> pure end+        Nothing+          | u == 0 -> pure 0+          | otherwise -> do+              v <- VGM.read suffixVec u+              nextM c v++  fix $ \popLoop -> do+    q <- Q.popFront que+    case q of+      Nothing -> pure ()+      Just u -> do+        -- visit neighbors+        for_ (HM.toList (trie VG.! u)) $ \(!c, !v) -> do+          Q.pushBack que v++          -- find the longest suffix to continue with `c`+          !suffix <- nextM c =<< VGM.read suffixVec u+          VGM.write suffixVec v suffix++          -- find the longest suffix that matches to a pattern+          let suffixPattern = patternMap VG.! suffix+          output <-+            if suffixPattern /= -1+              then pure suffix+              else VGM.read outputVec suffix+          VGM.write outputVec v output++        -- loop+        popLoop++  (,) <$> VU.unsafeFreeze suffixVec <*> VU.unsafeFreeze outputVec
+ src/AtCoder/Extra/DsuMonoid.hs view
@@ -0,0 +1,248 @@+{-# LANGUAGE RecordWildCards #-}++-- | A disjoint set union with commutative monoid values associated with each group.+--+-- ==== __Example__+--+-- >>> import AtCoder.Extra.DsuMonoid qualified as Dm+-- >>> import Data.Semigroup (Sum (..))+-- >>> import Data.Vector.Unboxed qualified as VU+-- >>> dsu <- Dm.build $ VU.generate 4 Sum+-- >>> Dm.merge dsu 0 1+-- 0+--+-- >>> Dm.read dsu 0+-- Sum {getSum = 1}+--+-- >>> Dm.read dsu 1+-- Sum {getSum = 1}+--+-- >>> Dm.mergeMaybe dsu 0 2+-- Just 0+--+-- >>> Dm.read dsu 0+-- Sum {getSum = 3}+--+-- @since 1.5.3.0+module AtCoder.Extra.DsuMonoid+  ( -- * Disjoint set union+    DsuMonoid (dsuDm, mDm),++    -- * Constructors+    new,+    build,++    -- * Merging+    merge,+    mergeMaybe,+    merge_,++    -- * Leader+    leader,++    -- * Component information+    same,+    size,+    groups,++    -- * Monoid values+    read,+    unsafeRead,+    unsafeWrite,+  )+where++import AtCoder.Dsu qualified as Dsu+import Control.Monad.Primitive (PrimMonad, PrimState, stToPrim)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable qualified as VGM+import Data.Vector.Unboxed qualified as VU+import Data.Vector.Unboxed.Mutable qualified as VUM+import GHC.Stack (HasCallStack)+import Prelude hiding (read)++-- | A disjoint set union with commutative monoid values associated with each group.+--+-- @since 1.5.3.0+data DsuMonoid s a = DsuMonoid+  { -- | The original DSU.+    --+    -- @since 1.5.3.0+    dsuDm :: {-# UNPACK #-} !(Dsu.Dsu s),+    -- | Commutative monoid values for each group.+    --+    -- @since 1.5.3.0+    mDm :: !(VUM.MVector s a)+  }++-- | Creates an undirected graph with \(n\) vertices and \(0\) edges.+--+-- ==== Constraints+-- - \(0 \le n\)+--+-- ==== Complexity+-- - \(O(n)\)+--+-- @since 1.5.3.0+{-# INLINE new #-}+new :: (PrimMonad m, Monoid a, VU.Unbox a) => Int -> m (DsuMonoid (PrimState m) a)+new n+  | n >= 0 = build $ VU.replicate n mempty+  | otherwise = error $ "AtCoder.Extra.DsuMonoid: given negative size (`" ++ show n ++ "`)"++-- | Creates an undirected graph with \(n\) vertices and \(0\) edges.+--+-- ==== Constraints+-- - \(0 \le n\)+--+-- ==== Complexity+-- - \(O(n)\)+--+-- @since 1.5.3.0+{-# INLINE build #-}+build :: (PrimMonad m, Semigroup a, VU.Unbox a) => VU.Vector a -> m (DsuMonoid (PrimState m) a)+build ms = stToPrim $ do+  dsuDm <- Dsu.new $ VU.length ms+  mDm <- VU.thaw ms+  pure $ DsuMonoid {..}++-- | Adds an edge \((a, b)\). If the vertices \(a\) and \(b\) are in the same connected component, it+-- returns the representative (`leader`) of this connected component. Otherwise, it returns the+-- representative of the new connected component.+--+-- ==== Constraints+-- - \(0 \leq a < n\)+-- - \(0 \leq b < n\)+--+-- ==== Complexity+-- - \(O(\alpha(n))\) amortized+--+-- @since 1.5.3.0+{-# INLINEABLE merge #-}+merge :: (HasCallStack, PrimMonad m, Semigroup a, VU.Unbox a) => DsuMonoid (PrimState m) a -> Int -> Int -> m Int+merge DsuMonoid {..} a b = stToPrim $ do+  r1 <- Dsu.leader dsuDm a+  r2 <- Dsu.leader dsuDm b+  if r1 == r2+    then pure r1+    else do+      !m1 <- VGM.read mDm r1+      !m2 <- VGM.read mDm r2+      r' <- Dsu.merge dsuDm a b+      VGM.write mDm r' $! m1 <> m2+      pure r'++-- | Adds an edge \((a, b)\). It returns the representative of the new connected component, or+-- `Nothing` if the two vertices are in the same connected component.+--+-- ==== Constraints+-- - \(0 \leq a < n\)+-- - \(0 \leq b < n\)+--+-- ==== Complexity+-- - \(O(\alpha(n))\) amortized+--+-- @since 1.2.4.0+{-# INLINEABLE mergeMaybe #-}+mergeMaybe :: (HasCallStack, PrimMonad m, Semigroup a, VU.Unbox a) => DsuMonoid (PrimState m) a -> Int -> Int -> m (Maybe Int)+mergeMaybe DsuMonoid {..} a b = stToPrim $ do+  r1 <- Dsu.leader dsuDm a+  r2 <- Dsu.leader dsuDm b+  if r1 == r2+    then pure Nothing+    else do+      !m1 <- VGM.read mDm r1+      !m2 <- VGM.read mDm r2+      r' <- Dsu.merge dsuDm a b+      VGM.write mDm r' $! m1 <> m2+      pure $ Just r'++-- | `merge` with the return value discarded.+--+-- ==== Constraints+-- - \(0 \leq a < n\)+-- - \(0 \leq b < n\)+--+-- ==== Complexity+-- - \(O(\alpha(n))\) amortized+--+-- @since 1.5.3.0+{-# INLINE merge_ #-}+merge_ :: (PrimMonad m, Semigroup a, VU.Unbox a) => DsuMonoid (PrimState m) a -> Int -> Int -> m ()+merge_ dsu a b = do+  _ <- merge dsu a b+  pure ()++-- | Returns whether the vertices \(a\) and \(b\) are in the same connected component.+--+-- ==== Constraints+-- - \(0 \leq a < n\)+-- - \(0 \leq b < n\)+--+-- ==== Complexity+-- - \(O(\alpha(n))\) amortized+--+-- @since 1.5.3.0+{-# INLINE same #-}+same :: (HasCallStack, PrimMonad m) => DsuMonoid (PrimState m) a -> Int -> Int -> m Bool+same dsu = Dsu.same (dsuDm dsu)++-- | Returns the representative of the connected component that contains the vertex \(a\).+--+-- ==== Constraints+-- - \(0 \leq a \lt n\)+--+-- ==== Complexity+-- - \(O(\alpha(n))\) amortized+--+-- @since 1.5.3.0+{-# INLINE leader #-}+leader :: (HasCallStack, PrimMonad m) => DsuMonoid (PrimState m) a -> Int -> m Int+leader dsu = Dsu.leader (dsuDm dsu)++-- | Returns the size of the connected component that contains the vertex \(a\).+--+-- ==== Constraints+-- -  \(0 \leq a < n\)+--+-- ==== Complexity+-- - \(O(\alpha(n))\)+--+-- @since 1.5.3.0+{-# INLINE size #-}+size :: (HasCallStack, PrimMonad m) => DsuMonoid (PrimState m) a -> Int -> m Int+size dsu = Dsu.size (dsuDm dsu)++-- | \O(n)\) Divides the graph into connected components and returns the vector of them.+--+-- More precisely, it returns a vector of the "vector of the vertices in a connected component".+-- Both of the orders of the connected components and the vertices are undefined.+--+-- @since 1.5.3.0+{-# INLINE groups #-}+groups :: (PrimMonad m) => DsuMonoid (PrimState m) a -> m (V.Vector (VU.Vector Int))+groups dsu = Dsu.groups (dsuDm dsu)++-- | \(O(1)\) Reads the group value of the \(k\)-th node.+--+-- @since 1.5.3.0+{-# INLINE read #-}+read :: (PrimMonad m, VU.Unbox a) => DsuMonoid (PrimState m) a -> Int -> m a+read DsuMonoid {..} i = do+  VGM.read mDm =<< Dsu.leader dsuDm i++-- | \(O(1)\) Reads the group value of the \(k\)-th node.+--+-- @since 1.5.3.0+{-# INLINE unsafeRead #-}+unsafeRead :: (PrimMonad m, VU.Unbox a) => DsuMonoid (PrimState m) a -> Int -> m a+unsafeRead DsuMonoid {..} i = do+  VGM.read mDm i++-- | \(O(1)\) Writes to the group value of the \(k\)-th node.+--+-- @since 1.5.3.0+{-# INLINE unsafeWrite #-}+unsafeWrite :: (PrimMonad m, VU.Unbox a) => DsuMonoid (PrimState m) a -> Int -> a -> m ()+unsafeWrite DsuMonoid {..} i x = do+  VGM.write mDm i x
src/AtCoder/Extra/KdTree.hs view
@@ -226,10 +226,10 @@       -- it's a leaf       | dataI /= -1 = (dataI, d)       -- look into the children-      | d0 < d1 = inner (2 * i + 0) $ inner (2 * i + 1) res-      | otherwise = inner (2 * i + 1) $ inner (2 * i + 0) res+      | d0 < d1 = inner (2 * i + 1) $ inner (2 * i + 0) res+      | otherwise = inner (2 * i + 0) $ inner (2 * i + 1) res       where         d = bestDistSquared i         dataI = dataKt VG.! i         d0 = bestDistSquared (2 * i + 0)-        d1 = bestDistSquared (2 * i + 0)+        d1 = bestDistSquared (2 * i + 1)
test/Main.hs view
@@ -4,7 +4,9 @@ import Test.Tasty.Ingredients.Rerun import Tests.Convolution qualified import Tests.Dsu qualified+import Tests.Extra.AhoCorasick qualified import Tests.Extra.Bisect qualified+import Tests.Extra.DsuMonoid qualified import Tests.Extra.DynLazySegTree qualified import Tests.Extra.DynLazySegTree.Persistent qualified import Tests.Extra.DynSegTree qualified@@ -67,7 +69,9 @@         testGroup "Dsu" Tests.Dsu.tests,         testGroup           "Extra"-          [ testGroup "Bisect" Tests.Extra.Bisect.tests,+          [ testGroup "AhoCorasick" Tests.Extra.AhoCorasick.tests,+            testGroup "Bisect" Tests.Extra.Bisect.tests,+            testGroup "DsuMonoid" Tests.Extra.DsuMonoid.tests,             testGroup "DynLazySegTree" Tests.Extra.DynLazySegTree.tests,             testGroup "DynLazySegTree.Persistent" Tests.Extra.DynLazySegTree.Persistent.tests,             testGroup "DynSegTree" Tests.Extra.DynSegTree.tests,
+ test/Tests/Extra/Vector.hs view
@@ -0,0 +1,171 @@+module Tests.Extra.Vector where++import AtCoder.Extra.Vector qualified as EV+import Control.Monad.ST (runST)+import Data.Functor.Identity (Identity, runIdentity)+import Data.List qualified as L+import Data.Vector qualified as V+import Data.Vector.Generic qualified as VG+import Data.Vector.Unboxed qualified as VU+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck as QC++prop_argsort :: [Int] -> QC.Property+prop_argsort xs =+  let lhs = VU.fromList . map snd . L.sort $ zip xs [0 :: Int ..]+      rhs = EV.argsort $ VU.fromList xs+   in lhs QC.=== rhs++prop_concatMapM :: [Int] -> QC.Property+prop_concatMapM xs =+  let f x = VU.fromList [x, x, x]+      vec = VU.fromList xs+      lhs = VU.concatMap f vec+      rhs = runST $ EV.concatMapM (pure . f) vec+   in lhs QC.=== rhs++prop_iconcatMap :: [Int] -> QC.Property+prop_iconcatMap xs =+  let f i x = VU.fromList [i + x, i + x, i + x]+      vec = VU.fromList xs+      lhs = VU.concat $ zipWith f [0 :: Int ..] xs+      rhs = runST $ EV.iconcatMapM (\i x -> pure (f i x)) vec+   in lhs QC.=== rhs++prop_iconcatMapM :: [Int] -> QC.Property+prop_iconcatMapM xs =+  let f i x = VU.fromList [i + x, i + x, i + x]+      vec = VU.fromList xs+      lhs = VU.concat $ zipWith f [0 :: Int ..] xs+      rhs = runST $ EV.iconcatMapM (\i x -> pure (f i x)) vec+   in lhs QC.=== rhs++prop_mapAccumL :: [Int] -> QC.Property+prop_mapAccumL xs =+  let f s x = (s * x, s + x)+      (!l1, !l2) = L.mapAccumL f (0 :: Int) xs+      (!r1, !r2) = EV.mapAccumL f (0 :: Int) $ VU.fromList xs+   in QC.conjoin [l1 QC.=== r1, VU.fromList l2 QC.=== r2]++-- | scanM etc.+prop_monadicScanlLike ::+  ((Int -> Int -> Int) -> Int -> VU.Vector Int -> VU.Vector Int) ->+  ((Int -> Int -> Identity Int) -> Int -> VU.Vector Int -> Identity (VU.Vector Int)) ->+  Int ->+  [Int] ->+  QC.Property+prop_monadicScanlLike ref acl x xs =+  let xs' = VU.fromList xs+      f = (+)+      mf x y = pure $ x + y+   in ref f x xs' QC.=== runIdentity (acl mf x xs')++-- | scanM1 etc.+prop_monadicScanl1Like ::+  ((Int -> Int -> Int) -> VU.Vector Int -> VU.Vector Int) ->+  ((Int -> Int -> Identity Int) -> VU.Vector Int -> Identity (VU.Vector Int)) ->+  QC.NonEmptyList Int ->+  QC.Property+prop_monadicScanl1Like ref acl (QC.NonEmpty xs) =+  let xs' = VU.fromList xs+      f = (+)+      mf x y = pure $ x + y+   in ref f xs' QC.=== runIdentity (acl mf xs')++prop_chunks :: QC.Positive Int -> [Int] -> QC.Property+prop_chunks (QC.Positive k) [] = EV.chunks k (VU.empty @Int) QC.=== V.empty+prop_chunks (QC.Positive k) xs =+  let res = EV.chunks k $ VU.fromList xs+      n = length xs+   in QC.conjoin+        [ V.sum (VG.map VG.length res) QC.=== n,+          V.all ((== k) . VG.length) (V.init res) QC.=== True,+          VG.concat (V.toList res) QC.=== VU.fromList xs+        ]++unit_maxRangeSum :: TestTree+unit_maxRangeSum = testCase "unit_maxRangeSum" $ do+  EV.maxRangeSum (VU.singleton (-1 :: Int)) @?= 0+  EV.maxRangeSum (VU.empty @Int) @?= 0++prop_maxRangeSum :: [Int] -> QC.Property+prop_maxRangeSum xs =+  let vec = VU.fromList xs+      lhs =+        let n = VU.length vec+            lrs = [(l, r) | l <- [0 .. n], r <- [l .. n]]+            eval (!l, !r) = VU.sum . VU.take (r - l) $ VU.drop l vec+         in maximum $ map eval lrs+      rhs = EV.maxRangeSum vec+   in lhs QC.=== rhs++unit_minRangeSum :: TestTree+unit_minRangeSum = testCase "unit_minRangeSum [1]" $ do+  EV.minRangeSum (VU.singleton (1 :: Int)) @?= 0+  EV.minRangeSum (VU.empty @Int) @?= 0++prop_minRangeSum :: [Int] -> QC.Property+prop_minRangeSum xs =+  let vec = VU.fromList xs+      lhs =+        let n = VU.length vec+            lrs = [(l, r) | l <- [0 .. n], r <- [l .. n]]+            eval (!l, !r) = VU.sum . VU.take (r - l) $ VU.drop l vec+         in minimum $ map eval lrs+      rhs = EV.minRangeSum vec+   in lhs QC.=== rhs++prop_slideMinIndices :: QC.Positive Int -> [Int] -> QC.Property+prop_slideMinIndices (QC.Positive k) xs =+  let vec = VU.fromList xs+   in slideMin k vec QC.=== EV.slideMinIndices k vec++prop_slideMaxIndices :: QC.Positive Int -> [Int] -> QC.Property+prop_slideMaxIndices (QC.Positive k) xs =+  let vec = VU.fromList xs+   in slideMax k vec QC.=== EV.slideMaxIndices k vec++slideMin :: Int -> VU.Vector Int -> VU.Vector Int+slideMin k xs+  | VU.null xs = VU.empty+  | k >= n = VU.singleton $ VU.minIndex xs+  | otherwise = VU.generate (n - (k - 1)) $ \l ->+      let slice = VU.take k $ VU.drop l xs+       in (+ l) $ VU.minIndex slice+  where+    n = VU.length xs++slideMax :: Int -> VU.Vector Int -> VU.Vector Int+slideMax k xs+  | VU.null xs = VU.empty+  | k >= n = VU.singleton $ VU.maxIndex xs+  | otherwise = VU.generate (n - (k - 1)) $ \l ->+      let slice = VU.take k $ VU.drop l xs+       in (+ l) $ VU.maxIndex slice+  where+    n = VU.length xs++tests :: [TestTree]+tests =+  [ QC.testProperty "argsort" prop_argsort,+    QC.testProperty "concatMapM" prop_concatMapM,+    QC.testProperty "iconcatMap" prop_iconcatMap,+    QC.testProperty "iconcatMapM" prop_iconcatMapM,+    QC.testProperty "mapAccumL" prop_mapAccumL,+    QC.testProperty "chunks" prop_chunks,+    QC.testProperty "prescanlM" (prop_monadicScanlLike VU.prescanl EV.prescanlM),+    QC.testProperty "prescanlM'" (prop_monadicScanlLike VU.prescanl' EV.prescanlM'),+    QC.testProperty "postscanlM" (prop_monadicScanlLike VU.postscanl EV.postscanlM),+    QC.testProperty "postscanlM'" (prop_monadicScanlLike VU.postscanl' EV.postscanlM'),+    QC.testProperty "scanlM" (prop_monadicScanlLike VU.scanl EV.scanlM),+    QC.testProperty "scanlM'" (prop_monadicScanlLike VU.scanl' EV.scanlM'),+    QC.testProperty "scanl1M" (prop_monadicScanl1Like VU.scanl1 EV.scanl1M),+    QC.testProperty "scanl1M'" (prop_monadicScanl1Like VU.scanl1' EV.scanl1M'),+    QC.testProperty "maxRangeSum" prop_maxRangeSum,+    QC.testProperty "minRangeSum" prop_minRangeSum,+    unit_maxRangeSum,+    unit_minRangeSum,+    QC.testProperty "slideMinIndices" prop_slideMinIndices,+    QC.testProperty "slideMaxIndices" prop_slideMaxIndices+  ]
+ test/Tests/Extra/Vector/Prim.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE ViewPatterns #-}++module Tests.Extra.Vector.Prim where++import AtCoder.Extra.Vector.Prim qualified as EV+import Control.Monad.ST (ST, runST)+import Data.Vector.Generic qualified as VG+import Data.Vector.Unboxed qualified as VU+import Test.Tasty+import Test.Tasty.QuickCheck as QC++prop_replicateM :: Int -> Int -> QC.Property+prop_replicateM n x = VU.replicate n x QC.=== runST (EV.replicateM n (pure x))++prop_generateM :: Int -> Int -> QC.Property+prop_generateM n x = VU.generate n (+ x) QC.=== runST (EV.generateM n (pure . (+ x)))++prop_iterateNM :: Int -> Int -> QC.Property+prop_iterateNM n x = VU.iterateN n (* 2) x QC.=== runST (EV.iterateNM n (pure . (* 2)) x)++prop_constructNM :: QC.NonNegative Int -> QC.Property+prop_constructNM (QC.NonNegative n) = VU.constructN n VG.length QC.=== runST (EV.constructNM n (pure . VG.length))++prop_constructrNM :: QC.NonNegative Int -> QC.Property+prop_constructrNM (QC.NonNegative n) = VU.constructrN n VG.length QC.=== runST (EV.constructrNM n (pure . VG.length))++prop_mapM :: [Int] -> QC.Property+prop_mapM (VU.fromList -> xs) = VU.map (* 2) xs QC.=== runST (EV.mapM (pure . (* 2)) xs)++-- prop_mapM_ :: [Int] -> QC.Property+-- prop_mapM_ xs =++prop_imapM :: [Int] -> QC.Property+prop_imapM (VU.fromList -> xs) = VU.imap (\i x -> 2 * (i + x)) xs QC.=== runST (EV.imapM (\i x -> pure (2 * (i + x))) xs)++-- prop_imapM_ :: [Int] -> QC.Property+-- prop_imapM_ xs =++prop_iforM :: [Int] -> QC.Property+prop_iforM (VU.fromList -> xs) = VU.imap (\i x -> 2 * (i + x)) xs QC.=== runST (EV.iforM xs (\i x -> pure (2 * (i + x))))++-- prop_iforM_ :: [Int] -> QC.Property+-- prop_iforM_ xs =++prop_zipWithM :: [Int] -> [Int] -> QC.Property+prop_zipWithM (VU.fromList -> xs) (VU.fromList -> ys) =+  VU.zipWith (*) xs ys QC.=== runST (EV.zipWithM (\x y -> pure (x * y)) xs ys)++-- prop_zipWithM_ :: [Int] -> [Int] -> QC.Property+-- prop_zipWithM_  (VU.fromList -> xs) (VU.fromList -> ys) =++prop_izipWithM :: [Int] -> [Int] -> QC.Property+prop_izipWithM (VU.fromList -> xs) (VU.fromList -> ys) =+  VU.izipWith (\i a b -> i + a * b) xs ys QC.=== runST (EV.izipWithM (\i a b -> pure (i + a * b)) xs ys)++-- prop_izipWithM_ :: [Int] -> [Int] -> QC.Property+-- prop_izipWithM_  (VU.fromList -> xs) (VU.fromList -> ys) =++prop_concatMapM :: [Int] -> QC.Property+prop_concatMapM xs =+  let f x = VU.fromList [x, x, x]+      vec = VU.fromList xs+      lhs = VU.concatMap f vec+      rhs = runST $ EV.concatMapM (pure . f) vec+   in lhs QC.=== rhs++prop_iconcatMap :: [Int] -> QC.Property+prop_iconcatMap xs =+  let f i x = VU.fromList [i + x, i + x, i + x]+      vec = VU.fromList xs+      lhs = VU.concat $ zipWith f [0 :: Int ..] xs+      rhs = runST $ EV.iconcatMapM (\i x -> pure (f i x)) vec+   in lhs QC.=== rhs++prop_iconcatMapM :: [Int] -> QC.Property+prop_iconcatMapM xs =+  let f i x = VU.fromList [i + x, i + x, i + x]+      vec = VU.fromList xs+      lhs = VU.concat $ zipWith f [0 :: Int ..] xs+      rhs = runST $ EV.iconcatMapM (\i x -> pure (f i x)) vec+   in lhs QC.=== rhs++prop_filterM :: [Int] -> QC.Property+prop_filterM (VU.fromList -> xs) = VU.filter odd xs QC.=== runST (EV.filterM (pure . odd) xs)++prop_mapMaybeM :: [Int] -> QC.Property+prop_mapMaybeM (VU.fromList -> xs) =+  let f x = if odd x then Just (2 * x) else Nothing+   in VU.mapMaybe f xs QC.=== runST (EV.mapMaybeM (pure . f) xs)++prop_imapMaybeM :: [Int] -> QC.Property+prop_imapMaybeM (VU.fromList -> xs) =+  let f i x = if odd i then Just (i * x) else Nothing+   in VU.imapMaybe f xs QC.=== runST (EV.imapMaybeM (\i -> pure . f i) xs)++-- | scanM etc.+prop_monadicScanlLike ::+  ((Int -> Int -> Int) -> Int -> VU.Vector Int -> VU.Vector Int) ->+  (forall s. (Int -> Int -> ST s Int) -> Int -> VU.Vector Int -> ST s (VU.Vector Int)) ->+  Int ->+  [Int] ->+  QC.Property+prop_monadicScanlLike ref acl x xs =+  let xs' = VU.fromList xs+      f = (+)+      mf x y = pure $ x + y+   in ref f x xs' QC.=== runST (acl mf x xs')++-- | scanM1 etc.+prop_monadicScanl1Like ::+  ((Int -> Int -> Int) -> VU.Vector Int -> VU.Vector Int) ->+  (forall s. (Int -> Int -> ST s Int) -> VU.Vector Int -> ST s (VU.Vector Int)) ->+  QC.NonEmptyList Int ->+  QC.Property+prop_monadicScanl1Like ref acl (QC.NonEmpty xs) =+  let xs' = VU.fromList xs+      f = (+)+      mf x y = pure $ x + y+   in ref f xs' QC.=== runST (acl mf xs')++tests :: [TestTree]+tests =+  [ QC.testProperty "replicateM" prop_replicateM,+    QC.testProperty "generateM" prop_generateM,+    QC.testProperty "iterateNM" prop_iterateNM,+    QC.testProperty "constructNM" prop_constructNM,+    QC.testProperty "constructrNM" prop_constructrNM,+    QC.testProperty "mapM" prop_mapM,+    -- QC.testProperty "mapM_" prop_mapM_,+    QC.testProperty "imapM" prop_imapM,+    -- QC.testProperty "imapM_" prop_imapM_,+    QC.testProperty "iforM" prop_iforM,+    -- QC.testProperty "iforM_" prop_iforM_,+    QC.testProperty "zipWithM" prop_zipWithM,+    -- QC.testProperty "zipWithM_" prop_zipWithM_,+    QC.testProperty "izipWithM" prop_izipWithM,+    -- QC.testProperty "izipWithM_" prop_izipWithM_,+    QC.testProperty "concatMapM" prop_concatMapM,+    QC.testProperty "iconcatMap" prop_iconcatMap,+    QC.testProperty "iconcatMapM" prop_iconcatMapM,+    QC.testProperty "filterM" prop_filterM,+    QC.testProperty "mapMaybeM" prop_mapMaybeM,+    QC.testProperty "imapMaybeM" prop_imapMaybeM,+    QC.testProperty "prescanlM" (prop_monadicScanlLike VU.prescanl EV.prescanlM),+    QC.testProperty "prescanlM'" (prop_monadicScanlLike VU.prescanl' EV.prescanlM'),+    QC.testProperty "postscanlM" (prop_monadicScanlLike VU.postscanl EV.postscanlM),+    QC.testProperty "postscanlM'" (prop_monadicScanlLike VU.postscanl' EV.postscanlM'),+    QC.testProperty "scanlM" (prop_monadicScanlLike VU.scanl EV.scanlM),+    QC.testProperty "scanlM'" (prop_monadicScanlLike VU.scanl' EV.scanlM'),+    QC.testProperty "scanl1M" (prop_monadicScanl1Like VU.scanl1 EV.scanl1M),+    QC.testProperty "scanl1M'" (prop_monadicScanl1Like VU.scanl1' EV.scanl1M')+  ]