module Data.List.HIUtils (
  -- * Sorting-like
  uniqSort
  , aggregate, aggregateBy, aggregateAL
  
  -- * Numeric lists
  , nat0, nat1
  , average , averageI
  , relFreq, relFreqAL
  
  -- * Lists with Eq
  , majority
    
  -- * Polymorphic list operations
  , oddIx, evenIx
  ) where
import Data.List
import Data.Ord
import Control.Arrow

-- TODO more checking

-- | Sort a list and leave out duplicates. Like @nub . sort@ but faster.
uniqSort :: (Ord a) => [a] -> [a]
uniqSort = map head . group . sort

-- TODO size constraint (nub complexity is :( )
prop_uniqSortIsNubSort :: [Int] -> Bool
prop_uniqSortIsNubSort a = uniqSort a == nub (sort a)

-- | Calculate the arithmetic mean. May not be numerically robust for some types and lists. Double and Rational should be fine most of the time.
average :: (Fractional a) => [a] -> a
average xs = (sum xs) / (genericLength xs)

-- | Shortcut for @average . map fromIntegral@. Check numerical robustness, see @average@ above.
averageI :: (Integral i, Fractional a) => [i] -> a
averageI = average . map fromIntegral

-- | Turn a list of integer frequencies into relative frequencies that sum up to 1. Frequencies should be nonnegative.
relFreq :: (Integral i, Fractional f) => [i] -> [f]
relFreq items = map divide items  
        where divide nom = fromIntegral nom / s
              s = fromIntegral (sum items)

-- | Turn an association list of integer frequencies into on of relative frequencies that sum up to 1. Frequencies should be nonnegative.
relFreqAL :: (Integral i, Fractional f) => [(a, i)] -> [(a, f)]
relFreqAL items = map (second divide) items
        where divide nom = fromIntegral nom / s
              s = fromIntegral . sum . map snd $ items

-- | Sort, then group
aggregate :: (Ord a) => [a] -> [[a]]
aggregate = aggregateBy compare

-- | Sort, then group
aggregateBy :: (a -> a -> Ordering) -> [a] -> [[a]]
aggregateBy x = groupBy (\a b -> x a b == EQ) . sortBy x

-- | Aggregate an association list, such that keys become unique.
aggregateAL :: (Ord a) => [(a,b)] -> [(a,[b])]
aggregateAL = map (fst . head &&& map snd) . aggregateBy (comparing fst)

-- | Find the most frequently occurring element. TODO: rewrite for Eq
majority :: (Ord a) => [a] -> a
majority = head . maximumBy (comparing length) . aggregate

-- | Infinite integer sequence of the natural numbers, starting at 0
nat0 :: (Num n) => [n]
nat0 = iterate (+1) 0

-- | Infinite integer sequence of the natural numbers, starting at 1
nat1 :: (Num n) => [n]
nat1 = iterate (+1) 1

-- | Select odd-indexed list elements where index of head is 0
oddIx = map snd . filter (odd . fst) . zip nat0

-- | Select even-indexed list elements where index of head is 0
evenIx = map snd . filter (even . fst) . zip nat0