My idea was to get all IDs from Terminations using a map and then filtering items with that ID from Infections using something like "in" from python but that solution seems to be way too "OOP".
Not at all, you have the right idea, in fact! It looks like you just need some help expressing it.
You can change your code to use the elem function, which is akin to Python’s in operator, but works on any foldable container with equatable elements:
elem :: (Foldable t, Eq a) => a -> t a -> Bool
Here you’ll be using it with t ~ [] and a ~ InfectionId, so it will have the specialised type elem :: InfectionId -> [InfectionId] -> Bool.
If you want to return just the IDs, not the whole Infection value, you can extract the IDs first with 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
Recall that the filter condition indicates which elements to keep, not which ones to filter out, so for the active cases you want to add a not. My mnemonic for this is “a coffee filter lets coffee through” (so filter even lets even values through).
You can simplify this in a few ways, though. First, you can make the data types into records and give each field a name so you can extract it (or update it) more conveniently:
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)
The mapping & filtering can be expressed with a list comprehension, and the not and elem functions can be combined into notElem for convenience. Putting these together:
activeCases :: Infections -> Terminations -> [InfectionId]
activeCases infections terminations =
[ infectionId infection
| infection <- infections
, infectionId infection `notElem` terminationIds
]
where
terminationIds =
[ terminationInfection termination
| termination <- terminations
]
You can still use pattern-matching in the comprehension if you prefer:
-- Positional matching:
[ i
| Infection i _ _ <- infections
, i `notElem` terminationIds
]
-- Named matching:
[ i
, Infection { infectionId = i } <- infections
, i `notElem` terminationIds
]
There are higher-level approaches as well. Scanning linearly over the list of Termination IDs for each Infection ID is O(n2), but what you really want here is a set difference, which can be computed more efficiently using map & set data structures such as Data.Map and Data.Set (or Data.IntMap and Data.IntSet) from containers. For example, if you store your data as a map from the ID to the record for that 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
]
Then the set of active cases is the key-set of the infections minus the set of terminated cases, which can be computed in O(n log n) time:
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
Or since a Termination is identified by an InfectionId as well, just the difference of the key sets:
Map.keysSet infections `Set.difference` Map.keysSet terminations