@@ -36,6 +36,10 @@ module Optics.IxFold
3636 , inoneOf
3737 , ifindOf
3838 , ifindMOf
39+ , imaximumOf
40+ , iminimumOf
41+ , imaximumByOf
42+ , iminimumByOf
3943
4044 -- * Combinators
4145 , ipre
@@ -367,5 +371,156 @@ ifindMOf o = \f -> ifoldrOf o
367371 (pure Nothing )
368372{-# INLINE ifindMOf #-}
369373
374+ -- | Obtain the maximum element (if any) targeted by an 'IxFold' along with its
375+ -- index.
376+ --
377+ -- In case of multiple maximum elements the last one is returned:
378+ --
379+ -- >>> imaximumOf ifolded "gazorpazorp"
380+ -- Just (7,'z')
381+ --
382+ -- so that the following property holds:
383+ --
384+ -- @
385+ -- 'fmap' 'snd' '.' 'imaximumOf' o ≡ 'maximumOf' o
386+ -- @
387+ --
388+ -- >>> let xs = "gazorpazorp"
389+ -- >>> fmap snd (imaximumOf ifolded xs) == maximumOf ifolded xs
390+ -- True
391+ --
392+ -- It holds even if the 'Ord' instance of the element type doesn't describe a
393+ -- total order, e.g. because of @NaN@ in floating point types:
394+ --
395+ -- >>> let ys = [2, 0/0, 1] :: [Double]
396+ -- >>> fmap snd (imaximumOf ifolded ys) == maximumOf ifolded ys
397+ -- True
398+ --
399+ -- Note: 'imaximumOf' on a valid 'Optics.IxLens.IxLens' or
400+ -- 'Optics.IxGetter.IxGetter' will always return 'Just' a value.
401+ --
402+ -- In the interest of efficiency, this operation has semantics more strict than
403+ -- strictly necessary.
404+ --
405+ -- @since 0.5
406+ imaximumOf
407+ :: (Is k A_Fold , is `HasSingleIndex ` i , Ord a )
408+ => Optic' k is s a -> s -> Maybe (i , a )
409+ imaximumOf o = ifoldlOf' o mf Nothing where
410+ -- The guard mirrors max, i.e. max x y = if x <= y then y else x.
411+ mf i acc a = case acc of
412+ Just (_, a') | not (a' <= a) -> acc
413+ _ -> Just (i, a)
414+ {-# INLINE imaximumOf #-}
415+
416+ -- | Obtain the minimum element (if any) targeted by an 'IxFold' along with its
417+ -- index.
418+ --
419+ -- In case of multiple minimum elements the first one is returned:
420+ --
421+ -- >>> iminimumOf ifolded "calamari"
422+ -- Just (1,'a')
423+ --
424+ -- so that the following property holds:
425+ --
426+ -- @
427+ -- 'fmap' 'snd' '.' 'iminimumOf' o ≡ 'minimumOf' o
428+ -- @
429+ --
430+ -- >>> let xs = "calamari"
431+ -- >>> fmap snd (iminimumOf ifolded xs) == minimumOf ifolded xs
432+ -- True
433+ --
434+ -- It holds even if the 'Ord' instance of the element type doesn't describe a
435+ -- total order, e.g. because of @NaN@ in floating point types:
436+ --
437+ -- >>> let ys = [2, 0/0, 1] :: [Double]
438+ -- >>> fmap snd (iminimumOf ifolded ys) == minimumOf ifolded ys
439+ -- True
440+ --
441+ -- Note: 'iminimumOf' on a valid 'Optics.IxLens.IxLens' or
442+ -- 'Optics.IxGetter.IxGetter' will always return 'Just' a value.
443+ --
444+ -- In the interest of efficiency, this operation has semantics more strict than
445+ -- strictly necessary.
446+ --
447+ -- @since 0.5
448+ iminimumOf
449+ :: (Is k A_Fold , is `HasSingleIndex ` i , Ord a )
450+ => Optic' k is s a -> s -> Maybe (i , a )
451+ iminimumOf o = ifoldlOf' o mf Nothing where
452+ -- The guard mirrors min, i.e. min x y = if x <= y then x else y.
453+ mf i acc a = case acc of
454+ Just (_, a') | a' <= a -> acc
455+ _ -> Just (i, a)
456+ {-# INLINE iminimumOf #-}
457+
458+ -- | Obtain the maximum element (if any) targeted by an 'IxFold' according to a
459+ -- user supplied 'Ordering' along with its index.
460+ --
461+ -- In case of multiple maximum elements the last one is returned:
462+ --
463+ -- >>> imaximumByOf ifolded (compare `on` length) ["hey","you","ham"]
464+ -- Just (2,"ham")
465+ --
466+ -- so that the following property holds:
467+ --
468+ -- @
469+ -- 'fmap' 'snd' '.' 'imaximumByOf' o cmp ≡ 'maximumByOf' o cmp
470+ -- @
471+ --
472+ -- >>> let xs = ["hey","you","ham"]
473+ -- >>> let cmp = compare `on` length
474+ -- >>> fmap snd (imaximumByOf ifolded cmp xs) == maximumByOf ifolded cmp xs
475+ -- True
476+ --
477+ -- In the interest of efficiency, this operation has semantics more strict than
478+ -- strictly necessary.
479+ --
480+ -- @since 0.5
481+ imaximumByOf
482+ :: (Is k A_Fold , is `HasSingleIndex ` i )
483+ => Optic' k is s a -> (a -> a -> Ordering ) -> s -> Maybe (i , a )
484+ imaximumByOf o = \ cmp ->
485+ let mf i acc a = case acc of
486+ Just (_, a') | cmp a' a == GT -> acc
487+ _ -> Just (i, a)
488+ in ifoldlOf' o mf Nothing
489+ {-# INLINE imaximumByOf #-}
490+
491+ -- | Obtain the minimum element (if any) targeted by an 'IxFold' according to a
492+ -- user supplied 'Ordering' along with its index.
493+ --
494+ -- In case of multiple minimum elements the first one is returned:
495+ --
496+ -- >>> iminimumByOf ifolded (compare `on` length) ["hey","you","ham"]
497+ -- Just (0,"hey")
498+ --
499+ -- so that the following property holds:
500+ --
501+ -- @
502+ -- 'fmap' 'snd' '.' 'iminimumByOf' o cmp ≡ 'minimumByOf' o cmp
503+ -- @
504+ --
505+ -- >>> let xs = ["hey","you","ham"]
506+ -- >>> let cmp = compare `on` length
507+ -- >>> fmap snd (iminimumByOf ifolded cmp xs) == minimumByOf ifolded cmp xs
508+ -- True
509+ --
510+ -- In the interest of efficiency, this operation has semantics more strict than
511+ -- strictly necessary.
512+ --
513+ -- @since 0.5
514+ iminimumByOf
515+ :: (Is k A_Fold , is `HasSingleIndex ` i )
516+ => Optic' k is s a -> (a -> a -> Ordering ) -> s -> Maybe (i , a )
517+ iminimumByOf o = \ cmp ->
518+ let mf i acc a = case acc of
519+ Just (_, a') | cmp a' a /= GT -> acc
520+ _ -> Just (i, a)
521+ in ifoldlOf' o mf Nothing
522+ {-# INLINE iminimumByOf #-}
523+
370524-- $setup
371525-- >>> import Optics.Core
526+ -- >>> import Data.Function (on)
0 commit comments