packages feed

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

{- |
Module      :  Data.Algorithms.Palindromes.Internal.PreProcessing
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

Functions for converting string input to different variants that can be used in the
algorithms.
-}
module Data.Algorithms.Palindromes.Internal.PreProcessing
    ( filterLetters
    , filterLetters'
    , textToWords
    , textToWordIndices
    , tryParseDNA
    ) where

import Data.Char (isAlphaNum, isSpace, toLower)
import Data.Maybe (fromMaybe)

import Data.Algorithms.Palindromes.DNA
    ( DNA
    , toDNA
    )
import Data.Algorithms.Palindromes.Internal.RangeFunctions (Range)

import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U

-- | A function that filters the string so that only letters remain.
filterLetters :: U.Vector Char -> U.Vector Char
filterLetters x = U.map toLower $ U.filter isAlphaNum x

{- | A function that filters the string so that only letters remain, but remembers the
original index of each character.
-}
filterLetters' :: U.Vector Char -> U.Vector Int
filterLetters' x = U.map fst $ U.filter (isAlphaNum . snd) (U.indexed (U.map toLower x))

{- | A function that filters the string so that only letters and spaces remain, then
splits the result on every space so that only words remain.
-}
textToWords :: U.Vector Char -> V.Vector String
textToWords x =
    V.fromList $ words $ map toLower $ filter (\a -> isAlphaNum a || isSpace a) $ U.toList x

-- | A function that returns a vector of ranges corresponding to the ranges of every word in the original input text.
textToWordIndices :: U.Vector Char -> V.Vector Range
textToWordIndices input = V.fromList $ map toWordRange $ wordIndices indexedCharacters
  where
    indexedCharacters :: [(Int, Char)]
    indexedCharacters = U.toList $ filterSpaceAndLetters input

    {- The input list is a word represented by the indices of every character of it in the
    original input string. Turn this list into a range, with only the indices of the first
    (inclusive) and last (exclusive) characters. -}
    toWordRange :: [Int] -> Range
    toWordRange [] = error "Empty string"
    toWordRange word@(firstIndex : _) =
        (firstIndex, last word + 1)

    -- The words function as written in Prelude, but on indexed characters.
    wordIndices :: [(Int, Char)] -> [[Int]]
    wordIndices s
        | null checking = []
        | otherwise = map fst word : wordIndices remaining
      where
        checking = dropWhile (isSpace . snd) s
        (word, remaining) = break (isSpace . snd) checking

    {- Keep only (lowercase) letters and spaces. Returns a vector of tuples with the
    original index of the charactar along with the character. -}
    filterSpaceAndLetters :: U.Vector Char -> U.Vector (Int, Char)
    filterSpaceAndLetters w =
        U.filter
            (\x -> (isAlphaNum . snd) x || (isSpace . snd) x)
            (U.indexed (U.map toLower w))

-- | If trying to parse the string to DNA would fail, throw a readable error.
tryParseDNA :: U.Vector Char -> U.Vector DNA
tryParseDNA input = fromMaybe (error "Invalid DNA string") (parseDna input)

{- | Filters the string so that only letters remain and parses it to DNA. Returns Nothing
if the string cannot be fully parsed to DNA.
-}
parseDna :: U.Vector Char -> Maybe (U.Vector DNA)
parseDna = toDNA . filterLetters