总结一下Haskell记录的列表

8

假设我有一份记录列表,我想通过取中位数来对其进行总结。更具体地说,假设我有

data Location = Location { x :: Double, y :: Double }

我有一组测量数据,想要将其总结为一个中位数位置,类似于以下内容:

Location (median (map x measurements)) (median (map y measurements))

那很好,但如果我有更嵌套的内容怎么办,例如:
data CampusLocation = CampusLocation { firstBuilding :: Location
                                      ,secondBuilding :: Location }

我有一个 CampusLocation 列表,我想要一个概述性的CampusLocation,其中中位数递归应用于所有字段。在 Haskell 中,最干净的方法是什么?使用 Lenses?还是 Uniplate?
编辑:奖励:
如果我们拥有一个隐式列表,而不是包含需要总结的字段的记录呢?例如:
data ComplexCampus = ComplexCampus { buildings :: [Location] }

如果我们假设每个建筑的长度都相同,那么我们该如何将一个[ComplexCampus]总结为一个ComplexCampus呢?


我突然想到这种东西可能适合“双重”镜头遍历:类型为 forall f. Traversable f => (f a -> b) -> (f s -> t) 的“应用器”。不知道是否有人已经考虑过这些。 - Ørjan Johansen
@ØrjanJohansen 我不确定这是否与此相关,但在distributive中有一个“cotraverse”。 - András Kovács
@AndrásKovács,看起来很相关,我应该记得那个。按照Kmett的通常命名方案,我建议的(除了Functor之外,他说这已经足够了)将是一个“cotraversal”。为了使问题中的类型实际上是“可分配的”,它们需要将Double更改为类型参数。 - Ørjan Johansen
从理论上讲,镜头在一般的Profuctor变换器Profunctor p => p a b -> p s t上运行得相当好,其中我们通过要求p是“可表示”的Profuctor来恢复Van Laarhoven公式。因此,在您的镜头中,您需要一个“核心表现”Profuctor。 - J. Abrahamson
1个回答

5
这里是一个使用Lenses w/ Uniplate实现的summarize :: [ComplexCampus] -> ComplexCampus函数,用于将一系列ComplexCampus对象汇总成一个ComplexCampus对象。
{-# Language TemplateHaskell,DeriveDataTypeable #-}
import Control.Lens
import Data.Data.Lens
import Data.Typeable
import Data.Data
import Data.List(transpose,genericLength)
data Location = Location { _x :: Double, _y :: Double } deriving(Show,Typeable,Data)


data CampusLocation =  CampusLocation { _firstBuilding :: Location, _firsecondBuilding :: Location }deriving(Show,Typeable,Data)
data ComplexCampus = ComplexCampus { _buildings :: [Location] } deriving(Show,Typeable,Data)


makeLenses ''Location
makeLenses ''CampusLocation
makeLenses ''ComplexCampus

l1 = Location 1 10
l2 = Location 2 20
l3 = Location 3 30


c1 = CampusLocation l1 l2
c2 = CampusLocation l2 l3
c3 = CampusLocation l1 l3
campusLocs = [c1,c2,c3]


c1' = ComplexCampus [l1, l2]
c2' = ComplexCampus [l2, l3]
c3' = ComplexCampus [l1, l3]
campusLocs' = [c1',c2',c3']


average l = (sum l) / (genericLength l)

-- returns average location for a list of locations
averageLoc locs = Location {
             _x = average $ locs ^.. biplate . x,
             _y = average $ locs ^.. biplate . y
             }


summarize :: [ComplexCampus] -> ComplexCampus
summarize ccs = ComplexCampus $ ccs ^.. biplate . buildings ^.. folding transpose . to averageLoc

在这里使用双板可能有些过度,但是无论如何,在averageLoc中,我们使用biplate处理位置列表以获取所有x字段和所有y字段。如果您想要将ComplexCampus汇总为单个Location,我们可以使用biplate从顶级ComplexBuilding中提取所有x值和y值。

例如:

campusLocs' ^.. biplate . x 给我们所有的x值,而 campusLocs' ^.. biplate . y 则给我们所有的y值。

同样,要获取所有位置,我们只需要执行:

(campusLocs' ^.. biplate) ::[Location]

或者,如果我们想要每个Double:

(campusLocs' ^.. biplate) ::[Double]


4
sum xs / genericLength xs 会遍历两次 xs 并且需要 O(n) 的空间。你应该使用一个累加器来计算总和和元素个数,并且在遍历时一次性完成,以达到常数空间复杂度。详细信息请参考 http://www.haskellforall.com/2013/08/composable-streaming-folds.html。 - Rein Henrichs
无论如何,原问题要求计算中位数,而不是平均数,我认为这基本上排除了将其转换为常量空间的可能性。 - Ørjan Johansen
2
foldl 现在支持镜头/遍历,因此您现在可以编写:let average = liftA2 (/) sum genericLength in Location <$> pretraverse x average <*> pretraverse y average :: Fold Location Location 以便只需一次严格地在常量空间内遍历列表。 - Gabriella Gonzalez
1
@GabrielGonzalez 我之前有些困惑,直到我谷歌搜索后才明白你在谈论基于 ReinHenrichs 博客文章中的思想的 foldl 包 - Ørjan Johansen
1
@GabrielGonzalez +1,foldl 应该始终附带一个指向 hackage 库的链接 :) - Konstantine Rybnikov
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接