我伸手去拿ArgMin
/ArgMax
当:
我想根据比较函数计算某些值的最小值/最大值(函数)
重新计算比较昂贵或笨拙,所以我想缓存它的结果;和/或
我想用幺半群foldMap
而不是显式/专门的minimumBy
/maximumBy
或来做它sortOn
,以使其灵活地适应未来的变化,例如不同的幺半群或并行化
这是我工作中最近一个真实世界示例的改编版findNextWorkerQueue
,它采用从工作人员到任务的映射,并找到具有最早第一个任务的工作人员,例如,给定以下输入:
工人 1:
- 时间 10:任务 A
- 时间 12:任务 B
- 时间 14:任务 C
工人 2:
- 时间 5:任务 D
- 时间 10:任务 E
- 时间 15:任务 F
工人 3:
它将产生一个开始时间为 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
使用它。时间签名在这里只是为了清楚起见。)Min
Bounded