@@ -28,7 +28,7 @@ module Options.Applicative.Internal
2828 , disamb
2929
3030 , mapParserOptions
31- , groupFst
31+ , groupFstAll
3232 ) where
3333
3434import Control.Applicative
@@ -41,8 +41,9 @@ import Control.Monad.Trans.Reader
4141 (mapReaderT , runReader , runReaderT , Reader , ReaderT , ask )
4242import Control.Monad.Trans.State (StateT , get , put , modify , evalStateT , runStateT )
4343import Data.Function (on )
44- import Data.List (groupBy )
45- import Data.Maybe (catMaybes )
44+ import qualified Data.List as L
45+ import Data.List.NonEmpty (NonEmpty ((:|) ))
46+ import qualified Data.List.NonEmpty as NE
4647
4748import Options.Applicative.Types
4849
@@ -275,11 +276,53 @@ hoistList = foldr cons empty
275276 where
276277 cons x xs = pure x <|> xs
277278
278- -- | Strips 'Nothing', then groups on the first element of the tuple.
279+ -- | Groups on the first element of the tuple. This differs from the simple
280+ -- @groupBy ((==) `on` fst)@ in that non-adjacent groups are __also__ grouped
281+ -- together. For example:
282+ --
283+ -- @
284+ -- groupFst = groupBy ((==) `on` fst)
285+ --
286+ -- let xs = [(1, "a"), (1, "b"), (3, "c"), (2, "d"), (3, "e"), (2, "f")]
287+ --
288+ -- groupFst xs === [[(1,"a"),(1,"b")],[(3,"c")],[(2,"d")],[(3,"e")],[(2,"f")]]
289+ -- groupFstAll xs === [[(1,"a"),(1,"b")],[(3,"c"),(3,"e")],[(2,"d"),(2,"f")]]
290+ -- @
291+ --
292+ -- Notice that the original order is preserved i.e. we do not first sort on
293+ -- the first element.
279294--
280295-- @since 0.19.0.0
281- groupFst :: (Eq a ) => [Maybe (a , b )] -> [[(a , b )]]
282- groupFst = groupBy ((==) `on` fst ) . catMaybes
296+ groupFstAll :: Ord a => [(a , b )] -> [[(a , b )]]
297+ groupFstAll =
298+ -- In order to group all (adjacent + non-adjacent) Eq elements together, we
299+ -- sort the list so that the Eq elements are in fact adjacent, _then_ group.
300+ -- We don't want to destroy the original order, however, so we add a
301+ -- temporary index that maintains this original order. The full logic is:
302+ --
303+ -- 1. Add index i that preserves original order.
304+ -- 2. Sort on tuple's fst.
305+ -- 3. Group by fst.
306+ -- 4. Sort by i, restoring original order.
307+ -- 5. Drop index i.
308+ fmap (NE. toList . dropIdx)
309+ . L. sortOn toIdx
310+ . NE. groupBy ((==) `on` fst')
311+ . L. sortOn fst'
312+ . addIdx
313+ where
314+ dropIdx :: NonEmpty (Int , (a , b )) -> NonEmpty (a , b )
315+ dropIdx = fmap (\ (_, y) -> y)
316+
317+ toIdx :: NonEmpty (Int , (a , b )) -> Int
318+ toIdx ((x, _) :| _) = x
319+
320+ -- Like fst, ignores our added index
321+ fst' :: (Int , (a , b )) -> a
322+ fst' (_, (x, _)) = x
323+
324+ addIdx :: [(a , b )] -> [(Int , (a , b ))]
325+ addIdx = zip [1 .. ]
283326
284327-- | Maps an Option modifying function over the Parser.
285328--
0 commit comments