packages feed

advent-of-code-ocr-0.1.0.0: src/Advent/OCR.hs

-- |
-- Module      : Advent.OCR
-- Copyright   : (c) Justin Le 2020
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- Library to parse "ASCII Art" letters from <https://adventofcode.com
-- Advent of Code> puzzles.  Compatible with all puzzles from 2015 to 2019.
--
module Advent.OCR (
  -- * Parse
    parseLetters
  , parseLettersWith
  , parseLettersEither
  , unsafeParseLetters
  , parseAsciiMap
  , asciiMapToLetters
  -- * Letter Map
  , LetterMap
  , defaultLetterMap
  -- ** Custom Letter Map
  , parseLetters'
  , parseLettersWith'
  , parseLettersEither'
  , unsafeParseLetters'
  , asciiMapToLetters'
  ) where

import           Advent.OCR.Internal
import           Data.Bifunctor
import           Data.Maybe
import           Data.Set            (Set)
import qualified Data.Set            as S

-- | A version of 'parseLettersWith'' accepting a custom 'LetterMap'
-- letterform database.
parseLettersWith'
    :: LetterMap        -- ^ database of letterforms
    -> (a -> Int)       -- ^ get X
    -> (a -> Int)       -- ^ get Y
    -> Set a
    -> Maybe String
parseLettersWith' lm f g = parseLettersV2 lm . S.map (\x -> V2 (f x) (g x))

-- | A version of 'parseLetters' that takes a set of any type of value, as
-- long as you provide functions to access the X and Y coordinates.
parseLettersWith
    :: (a -> Int)       -- ^ get X
    -> (a -> Int)       -- ^ get Y
    -> Set a
    -> Maybe String
parseLettersWith = parseLettersWith' defaultLetterMap

-- | A version of 'parseLetters'' accepting a custom 'LetterMap' letterform
-- database.
parseLetters'
    :: LetterMap            -- ^ database of letterforms
    -> Set (Int, Int)       -- ^ set of points
    -> Maybe String         -- ^ result, with unknown letters replaced with "?"
parseLetters' lm = parseLettersV2 lm . S.mapMonotonic (uncurry V2)

-- | The go-to default: given a set of point coordinates, parse it into
-- the string it represents.  Should be compatible with any Advent of Code
-- challenge from 2015 to 2019.
--
-- @
-- 'parseLetters' 'defaultLetterMap' myPoints
--
-- -- or, using Data.Default
-- 'parseLetters' 'Data.Default.Class.def' myPoints
-- @
--
-- A 'Nothing' means that there were no recognized letters found.  A 'Just'
-- means that least 50% of letter forms are recognized.  Unrecognized
-- characters will be replaced with "?"; for more information, use
-- 'parseLettersEither'.
--
-- This function is robust to changes in orientation or flipping, but will
-- be fastest if the coordinates are oriented with (0,0) on the upper left
-- corner.  However, because of this, it might return the wrong answer if
-- your coordinates are /not/ oriented this way and your result is
-- symmetrical: it'll always prioritize the interpretaion against (0,0)
-- upper-left orientation first.
parseLetters
    :: Set (Int, Int)       -- ^ set of points
    -> Maybe String         -- ^ result, with unknown letters replaced with "?"
parseLetters = parseLetters' defaultLetterMap

-- | A version of 'parseLettersEither'' accepting a custom 'LetterMap'
-- letterform database.
parseLettersEither'
    :: LetterMap            -- ^ database of letterforms
    -> Set (Int, Int)
    -> Maybe [Either (Set (Int, Int)) Char]
parseLettersEither' lm = (fmap . map . first . S.mapMonotonic) (\(V2 x y) -> (x, y))
                      . parseLettersEitherV2 lm
                      . S.mapMonotonic (uncurry V2)

-- | A version of 'parseLetters' returning a list of characters that were
-- either recognized or unrecognized; in the case of unrecognized
-- characters, returns the set of their coordinates but shifted to (0, 0)
-- as its upper left corner.
parseLettersEither
    :: Set (Int, Int)
    -> Maybe [Either (Set (Int, Int)) Char]
parseLettersEither = parseLettersEither' defaultLetterMap

-- | A version of 'unsafeParseLetters'' accepting a custom 'LetterMap'
-- letterform database.
unsafeParseLetters'
    :: LetterMap
    -> Set (Int, Int)
    -> String
unsafeParseLetters' lm =
      fromMaybe (error "Advent.OCR.unsafeParseLetters': Unable to parse letters")
    . parseLetters' lm

-- | A version of 'parseLetters' that will be undefined ('error') when no
-- parse is found.
unsafeParseLetters
    :: Set (Int, Int)
    -> String
unsafeParseLetters =
      fromMaybe (error "Advent.OCR.unsafeParseLetters: Unable to parse letters")
    . parseLetters

-- | Parse raw ASCII art into a set of points, usable with
-- 'parseLetters'.
parseAsciiMap
    :: Set Char             -- ^ characters to use as "on"/included
    -> String               -- ^ raw ASCII art
    -> Set (Int, Int)
parseAsciiMap c = S.mapMonotonic (\(V2 x y) -> (x, y)) . parseAsciiMapV2 c

-- | A version of 'asciiMapToLetters'' accepting a custom 'LetterMap'
-- letterform database.
asciiMapToLetters'
    :: Set Char             -- ^ characters to use as "on"/included in ASCII art
    -> LetterMap            -- ^ database of letterforms
    -> String               -- ^ raw ASCII art
    -> Maybe String
asciiMapToLetters' c lm = parseLettersV2 lm . parseAsciiMapV2 c

-- | Convenient all-in-one utility function combining 'parseAsciiMap' and
-- 'parseLetters', to directly parse ASCII art into its letters.
asciiMapToLetters
    :: Set Char             -- ^ characters to use as "on"/included in ASCII art
    -> String               -- ^ raw ASCII art
    -> Maybe String
asciiMapToLetters c = asciiMapToLetters' c defaultLetterMap