haskell - 用其他列表过滤自定义数据类型列表的最佳方法是什么(迭代它)
问题描述
我有一个关于用我自己的类型与另一个过滤列表的问题。
我来自python,所以haskell对我来说很难理解。
type PersonId = Integer
type PersonName = String
type InfectionId = Integer
type InfectionName = String
data TerminationType = Recovered | Dead
deriving (Eq, Show)
data Person = Person PersonId PersonName
deriving Show
data Infection = Infection InfectionId PersonId InfectionName
deriving Show
data Termination = Termination InfectionId TerminationType
deriving Show
type Persons = [Person]
type Infections = [Infection]
type Terminations = [Termination]
pers :: Persons
pers = [Person 1 "Augustin"
,Person 2 "Baltazar"
,Person 42 "Ctirad"
,Person 128 "Zdenek"
,Person 5 "Drahoslav"
]
infs :: Infections
infs = [Infection 2020 1 "COVID"
,Infection 2019 42 "COVID"
,Infection 1 5 "COVID"
,Infection 5 128 "rymicka"
,Infection 3 5 "astma"
,Infection 2 1 "astma"
,Infection 128 5 "zapal plic"
]
ters :: Terminations
ters = [Termination 2020 Dead
,Termination 2 Recovered
,Termination 2019 Recovered
,Termination 128 Dead
]
我正在尝试创建一个可以找到所有活动案例的功能。因此,它将根据当前感染列出已记录的感染列表并删除所有终止。
我的想法是使用地图从 Terminations 获取所有 ID,然后使用来自 python 的“in”之类的东西从 Infections 过滤具有该 ID 的项目,但该解决方案似乎太“OOP”了。
类似的东西。
activeCases :: Infections -> Terminations -> [InfectionId]
activeCases infec term = filter (infec.id in (map getId term)) infec
where getId (Termination y _) = y
我预定义的“pers,infs”和“ters”的最终结果是
activeCases infs ters ~>* [Infection 1 5 "COVID",Infection 5 128 "rymicka",Infection 3 5 "astma"]
有人可以告诉我解决此类问题的正确方法是什么吗?
解决方案
我的想法是使用地图从 Terminations 获取所有 ID,然后使用来自 python 的“in”之类的东西从 Infections 过滤具有该 ID 的项目,但该解决方案似乎太“OOP”了。
一点也不,事实上,你有正确的想法!看起来你只需要一些帮助来表达它。
您可以更改代码以使用该elem
函数,该函数类似于 Python 的in
运算符,但适用于任何具有相等元素的可折叠容器:
elem :: (Foldable t, Eq a) => a -> t a -> Bool
在这里,您将使用t ~ []
and a ~ InfectionId
,因此它将具有专门的类型elem :: InfectionId -> [InfectionId] -> Bool
。
如果您只想返回 ID,而不是整个Infection
值,您可以先使用map
.
activeCases :: Infections -> Terminations -> [InfectionId]
activeCases infections terminations
-- Select the IDs that are *not* found in the list of termination IDs.
= filter (\ i -> not (i `elem` terminationIds))
-- Extract the ID from each Infection.
(map infectionId infections)
where
terminationIds = map terminationId term
terminationId (Termination y _) = y
infectionId (Infection i _ _) = i
回想一下,filter
条件指示要保留哪些元素,而不是要过滤掉哪些元素,因此对于活动案例,您要添加一个not
. 我的记忆是“咖啡过滤器让咖啡通过”(所以filter even
让even
价值观通过)。
不过,您可以通过几种方式简化它。首先,您可以将data
类型制作成记录并为每个字段命名,以便您可以更方便地提取(或更新)它:
data Person = Person
{ personId :: PersonId
, personName :: PersonName
}
deriving (Show)
data Infection = Infection
{ infectionId :: InfectionId
, infectionPerson :: PersonId
, infectionName :: InfectionName
}
deriving (Show)
data Termination = Termination
{ terminationInfection :: InfectionId
, terminationType :: TerminationType
}
deriving (Show)
映射和过滤可以用列表推导来表示,并且为了方便可以将not
andelem
函数组合成。notElem
把这些放在一起:
activeCases :: Infections -> Terminations -> [InfectionId]
activeCases infections terminations =
[ infectionId infection
| infection <- infections
, infectionId infection `notElem` terminationIds
]
where
terminationIds =
[ terminationInfection termination
| termination <- terminations
]
如果您愿意,您仍然可以在理解中使用模式匹配:
-- Positional matching:
[ i
| Infection i _ _ <- infections
, i `notElem` terminationIds
]
-- Named matching:
[ i
, Infection { infectionId = i } <- infections
, i `notElem` terminationIds
]
还有更高层次的方法。Termination
对每个ID的 ID 列表进行线性扫描Infection
是 O(n 2 ),但您真正想要的是一个集合差异,可以使用 map & set 数据结构更有效地计算,例如Data.Map
and Data.Set
(or Data.IntMap
and Data.IntSet
) from containers
。例如,如果您将数据存储为从 ID 到该 ID 记录的映射:
import Data.Map (Map)
import Data.Set (Set)
type Persons = Map PersonId Person
type Infections = Map InfectionId Infection
type Terminations = Map InfectionId Termination
infs :: Infections
infs = Map.fromList
[ (i, infection)
| infection <-
[ Infection 2020 1 "COVID"
, Infection 2019 42 "COVID"
, Infection 1 5 "COVID"
, Infection 5 128 "rymicka"
, Infection 3 5 "astma"
, Infection 2 1 "astma"
, Infection 128 5 "zapal plic"
]
, let i = infectionId infection
]
那么活动病例集是感染的关键集减去终止病例集,可以在 O( n log n ) 时间内计算:
import qualified Data.Map as Map
import qualified Data.Set as Set
activeCases :: Infections -> Terminations -> Set InfectionId
activeCases infections terminations
= infectionIds `Set.difference` terminatedInfections
where
infectionIds = Map.keysSet infections
terminatedInfections = Set.fromList
$ map terminationInfection
$ Map.elems terminations
或者由于 aTermination
也由 a 标识InfectionId
,因此只是键集的差异:
Map.keysSet infections `Set.difference` Map.keysSet terminations
推荐阅读
- javascript - 标记窗口信息上的链接
- python - TypeError: 只需要 1 个参数(给定 2 个) TypeError: 'str' object is not callable
- python - 为什么 Python 将 windows 10 检测为 win32
- mysql - 索引了 1500 万客户,但查询仍然需要将近 5 分钟才能返回结果
- javascript - 如何在 JavaScript 中遍历存储在 firebase 数组中的对象?
- sqlite - Python 2.7 sqlite3 如何判断空字段
- ios - 动态设置 UIViewController 指针内联 - Objective C
- javascript - 输入值不保存,当推入数组时给出未定义的值
- javascript - 图表 JS 自定义标签不起作用
- typo3 - 多域和ipandlanguageredirect