Is there such a thing as maximumWith?

故事扮演 提交于 2021-02-11 12:50:32

问题


Specifically I'm searching for a function 'maximumWith',

maximumWith :: (Foldable f, Ord b) => (a -> b) -> f a -> a

Which behaves in the following way:

maximumWith length [[1, 2], [0, 1, 3]] == [0, 1, 3]
maximumWith null [[(+), (*)], []] == []
maximumWith (const True) x == head x

My use case is picking the longest word in a list.
For this I'd like something akin to maximumWith length.

I'd thought such a thing existed, since sortWith etc. exist.


回答1:


Let me collect all the notes in the comments together...

Let's look at sort. There are 4 functions in the family:

  • sortBy is the actual implementation.
  • sort = sortBy compare uses Ord overloading.
  • sortWith = sortBy . comparing is the analogue of your desired maximumWith. However, this function has an issue. The ranking of an element is given by applying the given mapping function to it. However, the ranking is not memoized, so if an element needs to compared multiple times, the ranking will be recomputed. You can only use it guilt-free if the ranking function is very cheap. Such functions include selectors (e.g. fst), and newtype constructors. YMMV on simple arithmetic and data constructors. Between this inefficiency, the simplicity of the definition, and its location in GHC.Exts, it's easy to deduce that it's not used that often.
  • sortOn fixes the inefficiency by decorating each element with its image under the ranking function in a pair, sorting by the ranks, and then erasing them.

The first two have analogues in maximum: maximumBy and maximum. sortWith has no analogy; you may as well write out maximumBy (comparing _) every time. There is also no maximumOn, even though such a thing would be more efficient. The easiest way to define a maximumOn is probably just to copy sortOn:

maximumOn :: (Functor f, Foldable f, Ord r) => (a -> r) -> f a -> a
maximumOn rank = snd . maximumBy (comparing fst) . fmap annotate
  where annotate e = let r = rank e in r `seq` (r, e)

There's a bit of interesting code in maximumBy that keeps this from optimizing properly on lists. It also works to use

maximumOn :: (Foldable f, Ord r) => (a -> r) -> f a -> a
maximumOn rank = snd . fromJust . foldl' max' Nothing
    where max' Nothing x = let r = rank x in r `seq` Just (r, x)
          max' old@(Just (ro, xo)) xn = let rn = rank xn
                                         in case ro `compare` rn of
                                                 LT -> Just (rn, xo)
                                                 _ -> old

These pragmas may be useful:

{-# SPECIALIZE maximumOn :: Ord r => (a -> r) -> [a] -> a #-}
{-# SPECIALIZE maximumOn :: (a -> Int) -> [a] -> a #-}



回答2:


HTNW has explained how to do what you asked, but I figured I should mention that for the specific application you mentioned, there's a way that's more efficient in certain cases (assuming the words are represented by Strings). Suppose you want

longest :: [[a]] -> [a]

If you ask for maximumOn length [replicate (10^9) (), []], then you'll end up calculating the length of a very long list unnecessarily. There are several ways to work around this problem, but here's how I'd do it:

data MS a = MS
  { _longest :: [a]
  , _longest_suffix :: [a]
  , _longest_bound :: !Int }

We will ensure that longest is the first of the longest strings seen thus far, and that longest_bound + length longest_suffix = length longest.

step :: MS a -> [a] -> MS a
step (MS longest longest_suffix longest_bound) xs =
    go longest_bound longest_suffix xs'
  where
    -- the new list is not longer
    go n suffo [] = MS longest suffo n
    -- the new list is longer
    go n [] suffn = MS xs suffn n
    -- don't know yet
    go !n (_ : suffo) (_ : suffn) =
      go (n + 1) suffo suffn

    xs' = drop longest_bound xs

longest :: [[a]] -> [a]
longest = _longest . foldl' step (MS [] [] 0)

Now if the second to longest list has q elements, we'll walk at most q conses into each list. This is the best possible complexity. Of course, it's only significantly better than the maximumOn solution when the longest list is much longer than the second to longest.



来源:https://stackoverflow.com/questions/52719426/is-there-such-a-thing-as-maximumwith

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!