【问题标题】:What is the purpose of the ArgMin and ArgMax type synonyms in Data.Semigroup?Data.Semigroup 中的 ArgMin 和 ArgMax 类型同义词的用途是什么?
【发布时间】:2020-11-20 12:54:41
【问题描述】:

Haskell 中的base 库在Data.Semigroup 中具有以下类型同义词:

type ArgMin a b = Min (Arg a b)

type ArgMax a b = Max (Arg a b) 

这里是黑线鳕的链接:ArgMinArgMax

这两种类型同义词的用途是什么?它们可以在哪里有效使用?

解释一下 argmin 和 argmax 函数在数学中的作用以及它们与这些类型同义词的关系可能会有所帮助。


这里有一些额外的信息,所以您不必跳到 Hackage。

这是Arg的定义:

-- | 'Arg' isn't itself a 'Semigroup' in its own right, but it can be
-- placed inside 'Min' and 'Max' to compute an arg min or arg max.
data Arg a b = Arg a b

它的文档字符串表明ArgMinArgMax 可以放在MinMax 内以计算arg min 或arg max。

MinMax 如下所示:

newtype Min a = Min { getMin :: a }

Semigroup 实例很有趣:

instance Ord a => Semigroup (Min a) where
  (<>) = coerce (min :: a -> a -> a)

看起来它正在使用min 作为(&lt;&gt;)

我们可以看看Ord 实例对于Arg 的样子,因为它与此处相关:

instance Ord a => Ord (Arg a b) where
  Arg a _ `compare` Arg b _ = compare a b
  min x@(Arg a _) y@(Arg b _)
    | a <= b    = x
    | otherwise = y
  max x@(Arg a _) y@(Arg b _)
    | a >= b    = x
    | otherwise = y

这似乎只对Arg 的第一个类型参数进行比较。

【问题讨论】:

标签: haskell monoids argmax semigroup type-synonyms


【解决方案1】:

我想这是 Haskell 中存在的那些东西之一,因为存在理论概念。我不确定这些类型是否有很多实际用途,但它们确实说明了半群和幺半群的概念在编程方面的广泛性。

例如,假设您需要选择两个名称中最长的一个,name1name2,它们都是 String 值。您可以为此使用ArgMaxSemigroup 实例:

Prelude Data.Semigroup> Max (Arg (length name1) name1) <> Max (Arg (length name2) name2)
Max {getMax = Arg 5 "Alice"}

之后,只需将"Alice" 从其容器中解开包装即可。

正如 Willem Van Onsem 在 cmets 中指出的那样,您可以使用 ArgMaxArgMin 根据项目的某些属性选择最大或最小项目,但仍保留原始项目。

【讨论】:

  • 我之前在真实代码中使用过。我们想实现一个类似分配器的东西,很容易将空间块添加回池中并从池中获取最大的空间块(但是在比较中我们想忽略更多关于块的信息) .我们使用ArgMax 的现有堆实现;完毕。另一种看待它的方式:它本质上是 decorate-sort-undecorate 模式,但用于 min/max 而不是 sort。
【解决方案2】:

它们的目的是实现minimumOn之类的东西:

minimumOn :: (Ord b, Foldable f) => (a -> b) -> f a -> Maybe a
minimumOn f = fmap (getArg  . getMin)
            . getOption
            . foldMap (Option . Just . Min . (Arg =<< f))
            --                         ^^^^^^^^^^
            --                           ArgMin
  where
    getArg (Arg _ x) = x

虽然这个实现可能看起来有点复杂,但使用像 monoids 这样的一般概念来实现通常会很有帮助。例如,在这种情况下,可以直接修改上述代码以在单次通过中计算最小值和最大值。

【讨论】:

    【解决方案3】:

    当我到达ArgMin / ArgMax 时:

    • 我想根据比较函数计算某些值的最小值/最大值(函数)

    • 比较是 costlyunwieldy 重新计算,所以我想缓存它的结果;和/或

    • 我想用foldMap 来做幺半群,而不是用明确/专门的minimumBy / maximumBysortOn,让它灵活地适应未来的变化,例如不同的幺半群或并行化

    这是我最近的一个真实世界示例的改编版,findNextWorkerQueue,它采用从工作人员到任务的映射,并找到最早执行第一个任务的工作人员,例如给定这个输入:

    • 工人 1:

      • 时间 10:任务 A
      • 时间 12:任务 B
      • 时间 14:任务 C
    • 工人 2:

      • 时间 5:任务 D
      • 时间 10:任务 E
      • 时间 15:任务 F
    • 工人 3:

      • 时间 22:任务 G
      • 时间 44:任务 H

    它将产生一个开始时间为 5 的工作队列,一个描述工人 2 的工作队列,第一个任务为 D,随后的任务为 E & F。

    {-# LANGUAGE ScopedTypeVariables #-}
    
    import Data.Map       (Map)
    import Data.Semigroup (Arg(..), Min(..), Option(..))
    import Data.Sequence  (Seq(Empty, (:<|)))
    
    import qualified Data.Map as Map
    
    -- An enumeration of computation units for running tasks.
    data WorkerId = …
    
    -- The timestamp at which a task runs.
    type Time = Int
    
    -- Some kind of task scheduled at a timestamp.
    data Scheduled task = Scheduled
      { schedAt   :: !Time
      , schedItem :: !task
      }
    
    -- A non-empty sequence of work assigned to a worker.
    data WorkQueue task = WorkQueue
      { wqId    :: !WorkerId
      , wqFirst :: !(Scheduled task)
      , wqRest  :: !(Seq (Scheduled task))
      }
    
    -- | Find the lowest worker ID with the first scheduled task,
    -- if any, and return its scheduled time and work queue.
    findNextWorkerQueue
      :: forall task
      .  Map WorkerId (Seq (Scheduled task))
      -> Maybe (Time, WorkerQueue task)
    findNextWorkerQueue
      = fmap getTimeAndQueue . getOption
      . foldMap (uncurry minWorkerTask) . Map.assocs
      where
    
        minWorkerTask
          :: WorkerId
          -> Seq (Scheduled task)
          -> Option (Min (Arg (Time, WorkerId) (WorkQueue task)))
        minWorkerTask wid tasks = Option $ case tasks of
          Empty -> Nothing
          t :<| ts -> Just $ Min $ Arg
            (schedTime t, wid)
            WorkQueue { wqId = wid, wqFirst = t, wqRest = ts }
    
        getTimeAndQueue
          :: Min (Arg (Time, WorkerId) (WorkQueue task))
          -> (Time, WorkQueue task)
        getTimeAndQueue (Min (Arg (time, _) queue))
          = (time, queue)
    

    (注意这是使用Option 来支持GHC 8.6;在GHC ≥8.8 中,Maybe 有一个改进的Monoid 实例,取决于Semigroup 而不是Monoid,所以我们可以将它与@ 一起使用987654334@ 没有施加Bounded 约束。拍号只是为了清楚起见。)

    【讨论】:

      猜你喜欢
      • 2013-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-26
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      • 2016-10-05
      相关资源
      最近更新 更多