packages feed

palindromes-1.1.0.0: src/Data/Algorithms/Palindromes/Internal/LinearAlgorithm.hs

{-# LANGUAGE MonoLocalBinds #-}

{- |
Module      :  Data.Algorithms.Palindromes.Internal.LinearAlgorithm
Copyright   :  (c) 2007 - 2025 Utrecht University (Department of Information and Computing Sciences) and Johan Jeuring
License     :  BSD3
Maintainer  :  johan@jeuring.net
Stability   :  provisional
Portability :  portable

This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) and Johan Jeuring

This module contains an implementation of a linear algorithm to find palindromes based on a paper by Johan Jeuring.
-}
module Data.Algorithms.Palindromes.Internal.LinearAlgorithm
    ( extendPalindromeS
    , finalPalindromesS
    , moveCenterS
    ) where

import Data.Algorithms.Palindromes.PalEq (PalEq (..), palEqToItselfAtIndex)

import qualified Data.Vector.Generic as G

-- | This function traverses input linearly, using an accumulator.
extendPalindromeS
    :: (PalEq a, G.Vector v a)
    => Bool
    -- ^ Indicates whether the input datatype is anti-reflexive.
    -> v a
    -- ^ Input, with only the elements we want to find palindromes in.
    -> Int
    -- ^ The rightmost index which is checked by the algorithm.
    -> [Int]
    -- ^ Length of palindromes that are already found.
    -> Int
    -- ^ The length of the palindrome currently being expanded.
    -> [Int]
    -- ^ The final list of maximal palindrome lengths at every center position.
extendPalindromeS onlyEvenPals input rightmost maximalPalindromesIn currentPalindrome
    | rightmost > lastPos =
        -- reached the end of the array
        finalPalindromesS
            onlyEvenPals
            currentPalindrome
            maximalPalindromesIn
            (currentPalindrome : maximalPalindromesIn)
    | rightmost - currentPalindrome == first
        || not ((input G.! rightmost) =:= (input G.! (rightmost - currentPalindrome - 1))) =
        -- the current palindrome extends to the start of the array, or it cannot be
        -- extended
        moveCenterS
            onlyEvenPals
            input
            rightmost
            (currentPalindrome : maximalPalindromesIn)
            maximalPalindromesIn
            currentPalindrome
    | otherwise =
        -- the current palindrome can be extended
        extendPalindromeS
            onlyEvenPals
            input
            (rightmost + 1)
            maximalPalindromesIn
            (currentPalindrome + 2)
  where
    first = 0 -- first index of the input
    lastPos = G.length input - 1 -- last index of the input

-- | If the current palindrome cannot be extended anymore, this function will move the centers one step
moveCenterS
    :: (PalEq a, G.Vector v a)
    => Bool
    -- ^ indicates whether the input datatype is anti-reflexive
    -> v a
    -- ^ input, with only the elements we want to find palindromes in
    -> Int
    -- ^ the rightmost index which is checked by the algorithm
    -> [Int]
    -- ^ length of all maximal palindromes that are already found
    -> [Int]
    {- ^ length of maximal palindromes that are already found, where head is always the
    maximal palindrome at the 'mirrored' index
    -}
    -> Int
    -- ^ the number of centers moveCenterS still has to find maximal palindromes for
    -> [Int]
    -- ^ the final list of maximal palindrome lengths
moveCenterS
    antiReflexive
    input
    rightmost
    maximalPalindromesIn
    maximalPalindromesIn'
    nrOfCenters
        | nrOfCenters == 0 =
            -- the last centre is on the last element: try to extend the tail
            let (newPalLength, inputForExtend) =
                    case (antiReflexive, palEqToItselfAtIndex input rightmost) of
                        {- Non-anti-reflexive type, but element at `rightmost` is not
                         PalEq to itself. We found an empty maximal palindrome. Add it to
                         the list and continue searching. -}
                        (False, False) -> (0, 0 : maximalPalindromesIn)
                        {- Non-anti-reflexive type, element at `rightmost` is
                        PalEq to itself. Found a palindrome of at least length 1, so try to
                        extend it. -}
                        (False, True) -> (1, maximalPalindromesIn)
                        {- Anti-reflexive type: only centers between elements matter. We
                        assume an empty palindrome and continue extending. -}
                        (True, _) -> (0, maximalPalindromesIn)
            in  extendPalindromeS
                    antiReflexive
                    input
                    (rightmost + 1)
                    inputForExtend
                    newPalLength
        | otherwise =
            {- move the centres one step and add the length of the longest palindrome to
            the centres -}
            case maximalPalindromesIn' of
                (headq : tailq) ->
                    if headq == nrOfCenters - centerfactor
                        then
                            {- The previous element in the centre list reaches exactly to the end of the
                            last tail palindrome. Use the mirror property of palindromes to find the
                            longest tail palindrome -}
                            extendPalindromeS
                                antiReflexive
                                input
                                rightmost
                                maximalPalindromesIn
                                (nrOfCenters - centerfactor)
                        else
                            {- move the centres one step and add the length of the longest palindrome to
                            the centres -}
                            moveCenterS
                                antiReflexive
                                input
                                rightmost
                                (min headq (nrOfCenters - centerfactor) : maximalPalindromesIn)
                                tailq
                                (nrOfCenters - centerfactor)
                [] -> error "extendPalindromeS: empty sequence"
      where
        {- If type is anti-reflexive, we can skip centers on elements, so take steps of
        size 2. -}
        centerfactor
            | antiReflexive = 2
            | otherwise = 1

{- | After the current palindrome reached the end of the input vector, this function will
find and return the final palindromes using the pal in pal property.
-}
finalPalindromesS
    :: Bool
    -- ^ indicates whether the input datatype is anti-reflexive.
    -> Int
    -- ^ number of centers that haven't been extended yet.
    -> [Int]
    {- ^ list of found palindrome lengths, where the head corresponds to the value which
    must be copied next.
    -}
    -> [Int]
    {- ^ the lengths of all the palindromes that are found, including palindromes found by
    this function. This is used as an accumulator.
    -}
    -> [Int]
    -- ^ the lengths of all found maximal palindromes in reverse order.
finalPalindromesS antiReflexive nrOfCenters maximalPalindromesIn acc
    | nrOfCenters == 0 =
        acc
    | nrOfCenters > 0 =
        case maximalPalindromesIn of
            (p : ps) ->
                {- for a center, add the (truncated) copied palindrome to the accumulator
                and recurse over nrOfCenters. -}
                finalPalindromesS
                    antiReflexive
                    (nrOfCenters - centerfactor)
                    ps
                    {- the palindrome cannot be bigger than the remaining number of
                    centers minus the centerfactor. Truncate the palindrome if necessary.
                    -}
                    (min p (nrOfCenters - centerfactor) : acc)
            [] -> error "finalPalindromesS: empty sequence"
    | otherwise = error "finalPalindromesS: input < 0"
  where
    {- If type is anti-reflexive, we can skip centers on elements, so take steps of
    size 2. -}
    centerfactor
        | antiReflexive = 2
        | otherwise = 1