packages feed

finite-1.5.0.0: src/lib/Finite/Collection.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Finite.Collection
-- Maintainer  :  Felix Klein
--
-- Allows to extend a finite instance from a single bound to a
-- collection of bounds, given as a finite ranged array.
--
-----------------------------------------------------------------------------

{-# LANGUAGE

    ImplicitParams
  , LambdaCase
  , MultiParamTypeClasses
  , ScopedTypeVariables

  #-}

-----------------------------------------------------------------------------

module Finite.Collection where

-----------------------------------------------------------------------------

import Finite.Class
  ( Finite
  , elements
  , offset
  , value
  , index
  , withBounds
  )

import Data.Array.IArray
  ( Array
  , Ix
  , (!)
  , inRange
  , assocs
  , range
  , bounds
  )

import Control.Exception
  ( assert
  )

-----------------------------------------------------------------------------

-- | The 'Collection' type provides a set of items, each assigning an
-- index of type @i@ to a value of type @a@.
data Collection i a = Item i a
  deriving
    ( -- | Equality can be checked for collections, if the index type
      -- and the elements can be checked for equality.
      Eq
    , -- | Order can be checked for collections, if the index type and
      -- the elements can be oredered.
      Ord
    , -- | Show a collection through its default constructor.
      Show
    )

-----------------------------------------------------------------------------

-- | Collections are used to extend Finite-Type / Context-Bounds pairs
-- to an array of bounds. At the same time the finite type is extended
-- to a collection of items that range over the same set of indices as
-- the bounds. Since the 'FiniteBounds' parameter always gives a
-- finite sized array of bounding parameters, it is guaranteed that
-- the connected collection has a finite bound as well.
instance (Ix i, Finite b a) => Finite (Array i b) (Collection i a) where
  elements _ = sum $ (`withBounds` elements a) . snd <$> assocs ?bounds

  index (Item j v) = o + idx
   where
    -- array bounds
    (l, u) = bounds ?bounds
    -- list of indicies that appear before j
    ys = assert (inRange (l, u) j) $ init $ range (l, j)
    -- offset induces by these indices
    o = sum $ map ((`withBounds` elements a) . (?bounds !)) ys
    -- index of v with the bounds at position j
    idx = withBounds (?bounds ! j) $ index v - offset a

  value n =
    assert (n >= 0 && n < elements (Collection i a))
      $ withBounds (?bounds ! j) $ Item j $ value (m + offset a)
   where
    -- target array index and reminder used as sub-index
    (j, m) = position n $ range $ bounds ?bounds

    position n = \case
      []                 -> assert False undefined
      x : xr | m <= n    -> position (n - m) xr
             | otherwise -> (x, n)
       where
        m = withBounds (?bounds ! x) $ elements a

-----------------------------------------------------------------------------