packages feed

hgeometry-combinatorial-0.12.0.0: src/Data/Vector/Circular/Util.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
--------------------------------------------------------------------------------
-- |
-- Module      :  Data.Vector.Circular.Util
-- Copyright   :  (C) Frank Staals, David Himmelstrup
-- License     :  see the LICENSE file
-- Maintainer  :  David Himmelstrup
--------------------------------------------------------------------------------
module Data.Vector.Circular.Util where

import           Algorithms.StringSearch.KMP (isSubStringOf)
import           Control.Lens
import           Data.Ext
import           Data.Maybe
import           Data.Semigroup.Foldable
import qualified Data.Vector as V
import           Data.Vector.Circular as CV
import qualified Data.Vector.NonEmpty as NV
import           Test.QuickCheck (Arbitrary (..), NonEmptyList (..))


-- FIXME: Upstream this to the non-empty vector library?
instance Foldable1 NV.NonEmptyVector

-- | Access the ith item in the CircularVector (w.r.t the rotation) as a lens
item   :: Int -> Lens' (CircularVector a) a
item i = lens (`CV.index` i) (\s x -> unsafeFromVector (toVector s V.// [(i,x)]))

-- | All elements, starting with the focus, going to the right
--
-- >>> rightElements $ unsafeFromList [3,4,5,1,2]
-- [3,4,5,1,2]
rightElements :: CircularVector a -> NV.NonEmptyVector a
rightElements = toNonEmptyVector

-- | All elements, starting with the focus, going to the left
--
-- >>> leftElements $ unsafeFromList [3,4,5,1,2]
-- [3,2,1,5,4]
leftElements :: CircularVector a -> NV.NonEmptyVector a
leftElements v = NV.generate1 (length v) (CV.index v . negate)

-- | Finds an element in the CircularVector
--
-- >>> findRotateTo (== 3) $ unsafeFromList [1..5]
-- Just (CircularVector {vector = [1,2,3,4,5], rotation = 2})
-- >>> findRotateTo (== 7) $ unsafeFromList [1..5]
-- Nothing
findRotateTo   :: (a -> Bool) -> CircularVector a -> Maybe (CircularVector a)
findRotateTo p (CircularVector v _rot) = CircularVector v <$> NV.findIndex p v

-- | Test if the circular list is a cyclic shift of the second
-- list.
--
-- Running time: \(O(n+m)\), where \(n\) and \(m\) are the sizes of
-- the lists.
isShiftOf         :: Eq a => CircularVector a -> CircularVector a -> Bool
xs `isShiftOf` ys = let twice zs    = let zs' = leftElements zs in zs' <> zs'
                        once        = leftElements
                        check as bs = isJust $ once as `isSubStringOf` twice bs
                    in length xs == length ys && check xs ys

instance Arbitrary a => Arbitrary (CircularVector a) where
  arbitrary = unsafeFromList <$> (getNonEmpty <$> arbitrary)

-- | label the circular vector with indices, starting from zero at the
-- current focus, going right.
--
-- Running time: \(O(n)\)
withIndicesRight                      :: CircularVector a -> CircularVector (Int :+ a)
withIndicesRight (CircularVector v s) = CircularVector v' s
  where
    n  = length v
    v' = NV.imap (\i x -> ((i-s) `mod` n) :+ x) v