packages feed

edits-0.1.0.0: src/Data/Text/Costs.hs

{-# LANGUAGE RecordWildCards #-}

-- | This module supports a general definition of costs for performing edit operations
--   on 2 lists of elements
module Data.Text.Costs where

import Protolude

-- | Current operation in a cost matrix and current cost
data Cost
  = Insertion Int
  | Deletion Int
  | Substitution Int
  | NoAction Int
  deriving (Eq, Show)

-- | Return the cost of an operation
cost :: Cost -> Int
cost (Insertion c) = c
cost (Deletion c) = c
cost (Substitution c) = c
cost (NoAction c) = c

-- | Nicer display for a cost
showCost :: Cost -> Text
showCost (Insertion c) = "+ " <> show c
showCost (Deletion c) = "- " <> show c
showCost (Substitution c) = "~ " <> show c
showCost (NoAction c) = "o " <> show c

-- | This component contains functions to evaluate the cost of
--   substituting, inserting, deleting an element
data Costs a = Costs
  { substitutionCost :: a -> a -> Int,
    insertionCost :: a -> Int,
    deletionCost :: a -> Int,
    lowerCost :: a -> a -> Int -> Int -> Int -> Cost
  }

-- | Classic costs for the Levenshtein distance
--   applied to characters in a piece of Text
textLevenshteinCosts :: Costs Char
textLevenshteinCosts = levenshteinCosts @Char

-- | Classic costs for the Levenshtein distance
levenshteinCosts :: forall a. (Eq a) => Costs a
levenshteinCosts = Costs {..}
  where
    substitutionCost :: a -> a -> Int
    substitutionCost a1 a2 = if a1 == a2 then 0 else 1

    insertionCost :: a -> Int
    insertionCost = const 1

    deletionCost :: a -> Int
    deletionCost = const 1

    lowerCost :: a -> a -> Int -> Int -> Int -> Cost
    lowerCost a1 a2 del subst ins = do
      let (opDel, opSubst, opIns) = (Deletion del, Substitution subst, Insertion ins)
      if ins < del
        then (if (ins < subst) || (ins == subst && a1 == a2) then opIns else opSubst)
        else (if (del < subst) || (del == subst && a1 == a2) then opDel else opSubst)