packages feed

palindromes-1.0: src/Data/Algorithms/Palindromes/Streaming.hs

{-# LANGUAGE BangPatterns #-}

{- |
Module      :  Data.Algorithms.Palindromes.Streaming
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 functions for streaming the search for palindromes. This is used for
the progress bar.
-}
module Data.Algorithms.Palindromes.Streaming (findPalindromesStream, findPalindromesWithProgressBar, findPalindromesVisualised) where

import Control.Monad.IO.Class (liftIO)
import System.IO (hFlush)

import Data.Algorithms.Palindromes.Finders
    ( Algorithm (..)
    , Variant (..)
    , findPalindromes
    )
import Data.Algorithms.Palindromes.Output (longest)
import Data.Algorithms.Palindromes.Palindrome (Palindrome (..), getLength)

import qualified Data.Conduit as C
import qualified Data.Conduit.Combinators as C
import qualified Data.Vector as V
import qualified System.IO as Sys

-- | Streams the result of the given settings
findPalindromesStream
    :: Variant -> Algorithm -> Int -> String -> C.ConduitT () Palindrome IO ()
findPalindromesStream variant algorithm minlen input =
    C.yieldMany $ findPalindromes variant algorithm minlen input

{- | Returns the result of finding palindromes with the settings (The first 3 params) on the string
whilst diplaying the intermediate progress using the given function
-}
findPalindromesVisualised
    :: Variant
    -> Algorithm
    -> Int
    -> Bool
    -- ^ Enable filtering to only return the longest palindromes
    -> String
    -> (Float -> IO ())
    -- ^ Function that defines how to visualise progress
    -> IO [Palindrome]
findPalindromesVisualised variant algorithm minLength filterLongest input visualiseProgress =
    do
        let chunkSize = 100
        -- Used for minimum detail level for progress, only use smaller than 2 if user explicitly overrides default
        let streamMinLength = min minLength 2
        let !inputLength = length input
        visualiseProgress 0
        result <-
            C.runConduit $
                findPalindromesStream variant algorithm streamMinLength input
                    C..| C.conduitVector chunkSize -- Chunk result stream
                    C..| calcVisualiseProgress inputLength algorithm visualiseProgress
                    C..| C.concat
                    C..| C.filter -- Filter to actual given filter size
                        ((minLength <=) . getLength)
                    C..| if filterLongest
                        then
                            C.foldl longest []
                        else C.sinkList
        return $ if filterLongest then reverse result else result

{- | Consumes a Vector of Palindrome and prints the progress made
based on the centre of the palindrome range compared to the input length.
Then forces the values in the Vector to ensure progress has been made and passes them on.
-}
calcVisualiseProgress
    :: Int
    -- ^ Max length of the input vector, needed to calculate percentage
    -> Algorithm
    -- ^ Required because the Approximate algorithm returns the palindromes in reverse order
    -> (Float -> IO ())
    -- ^ Function that defines how to visualise the progress
    -> C.ConduitT (V.Vector Palindrome) (V.Vector Palindrome) IO ()
calcVisualiseProgress totalLen algorithm visualise = C.awaitForever $ \pals -> do
    let rawProgress = fromIntegral (sum $ palRangeInText (V.last pals)) / fromIntegral totalLen
    let progress = case algorithm of
            AlgApproximate _ _ -> 1 - rawProgress
            _ -> rawProgress
    liftIO $ visualise progress
    C.yield pals

-- | Wrapper for findPalindromesVisualised where the visualisation method is printProgressbar
findPalindromesWithProgressBar
    :: Variant
    -> Algorithm
    -> Int
    -> Bool
    -> String
    -> IO [Palindrome]
findPalindromesWithProgressBar variant algorithm minLength filterLongest input =
    findPalindromesVisualised
        variant
        algorithm
        minLength
        filterLongest
        input
        printProgressBar

{- | Prints a progress bar into the terminal with progress between 0 and 1,
then flushes stdout to force a write
-}
printProgressBar :: Float -> IO ()
printProgressBar progress = do
    let hashes = round $ progress * 40
    let dashes = 40 - hashes
    let percent = (round $ progress * 100) :: Int
    let bar =
            "["
                ++ replicate hashes '#'
                ++ replicate dashes '-'
                ++ "] "
                ++ show percent
                ++ "%"
    putStr $ "\r" ++ bar
    hFlush Sys.stdout