{-# LANGUAGE CPP #-}
{-# LANGUAGE Trustworthy #-}
-- |
-- Module      : Data.List.GroupBy
-- Description : A replacement for 'Data.List.groupBy'
-- Copyright   : (c) Donnacha Oisín Kidney, 2018
-- License     : MIT
-- Maintainer  : mail@doisinkidney.com
-- Stability   : experimental
-- Portability : portable
--
-- This module provides an alternative definition for
-- 'Data.List.groupBy' which does not require a transitive
-- equivalence predicate.
module Data.List.GroupBy
  (groupBy
  ,group)
  where

#if __GLASGOW_HASKELL__
import           GHC.Base (build, foldr
#if __GLASGOW_HASKELL__ >= 800
                           ,oneShot
#endif
                          )
import           Prelude  hiding (foldr)
#endif

-- | Groups adjacent elements according to some relation.
-- The relation can be an equivalence:
--
--
-- >>> groupBy (==) "aaabcccdda"
-- ["aaa","b","ccc","dd","a"]
--
-- >>> groupBy (==) []
-- []
--
-- However, it need not be. The function compares adjacent
-- elements only, so it can be used to find increasing
-- subsequences:
--
-- >>> groupBy (<=) [1,2,2,3,1,2,0,4,5,2]
-- [[1,2,2,3],[1,2],[0,4,5],[2]]
--
-- It is fully lazy:
--
-- >>> head (groupBy (==) (1:2:undefined))
-- [1]
--
-- >>> (head . head) (groupBy undefined (1:undefined))
-- 1
--
-- >>> (head . head . tail) (groupBy (==) (1:2:undefined))
-- 2
--
-- prop> xs === concat (groupBy (applyFun2 p) xs)
-- prop> all (not . null) (groupBy (applyFun2 p) xs)

groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _ [] = []
groupBy p' (x':xs') = (x' : ys') : zs'
  where
    (ys',zs') = go p' x' xs'
    go p z (x:xs)
      | p z x = (x : ys, zs)
      | otherwise = ([], (x : ys) : zs)
      where (ys,zs) = go p x xs
    go _ _ [] = ([], [])

#if __GLASGOW_HASKELL__
{-# NOINLINE [1] groupBy #-}
{-# INLINE [0] groupByFB #-}
{-# ANN groupByFB "hlint: ignore Redundant lambda" #-}
groupByFB
    :: (a -> a -> Bool)
    -> ([a] -> b -> b)
    -> a
    -> ((a -> Bool) -> ([a], b))
    -> (a -> Bool)
    -> ([a], b)
groupByFB p c =
    \x a ->
#if __GLASGOW_HASKELL__ >= 800
         oneShot
#endif
             (\q ->
                   let (ys,zs) = a (p x)
                   in if q x
                          then (x : ys, zs)
                          else ([], c (x : ys) zs))


{-# INLINE [0] constGroupBy #-}
constGroupBy :: a -> b -> ([c], a)
constGroupBy n = const ([], n)

{-# INLINE [0] constFalse #-}
constFalse :: a -> Bool
constFalse = const False

{-# INLINE [0] sndGroupBy #-}
sndGroupBy :: (a, b) -> b
sndGroupBy = snd
{-# RULES
"groupBy"     [~1] forall p xs. groupBy p xs = build (\c n -> sndGroupBy (foldr (groupByFB p c) (constGroupBy n) xs constFalse))
"groupByList" [1]  forall p xs. sndGroupBy (foldr (groupByFB p (:)) (constGroupBy []) xs constFalse) = groupBy p xs #-}

#else
{-# INLINE groupBy #-}
#endif

-- | Groups adjacent equal elements.
--
-- >>> group "aaabcccdda"
-- ["aaa","b","ccc","dd","a"]
group :: Eq a => [a] -> [[a]]
group = groupBy (==)
{-# INLINE group #-}

-- $setup
-- >>> import Test.QuickCheck
-- >>> import Test.QuickCheck.Function