Populating a list of tuples in a semantic way

2019-02-27 20:42发布

问题:

I'm working on a piece of code where I have to process lists of tuples where both the order and names of the "keys" (fsts of the tuples) match a certain template. I'm implementing fault tolerance by validating and (if needed) generating a valid list based on the input.

Here's an example of what I mean:

Given the template of keys, ["hello", "world", "this", "is", "a", "test"], and a list [("hello", Just 1), ("world", Just 2), ("test", Just 3)], passing it to my function validate would cause it to fail validation - as the order and values of the keys do not match up with the template.

Upon failing validation, I want to generate a new list, which would look like [("hello", Just 1), ("world", Just 2), ("this", Nothing), ("is", Nothing), ("a", Nothing), ("test", Just 3)].

I tried performing this last step using an (incomplete) list comprehension:

[(x, y) | x <- template, y <- l]

(Obviously, this is missing the step where empty entries would be replaced with Nothings, and works under the assumption that the input is of type [(String, Maybe Int)]).

What would be the easiest semantic way of doing this?

回答1:

You essentially want to map a function to your list of strings (which you call "template"), i.e. the function that

  • takes a string xs,
  • returns
    • (xs, Just n) if an integer n is associated to xs in your "list to validate",
    • (xs, Nothing) otherwise.

Here is one possible approach:

import Data.List     ( lookup )
import Control.Monad ( join )

consolidate :: [String] -> [(String, Maybe Int)] -> [(String, Maybe Int)]
consolidate temp l = map (\xs -> (xs, join $ lookup xs l)) temp

However, you will get faster lookup if you build a Map holding the key-value pairs of your association list (the "list to validate"):

import qualified Data.Map as M
import Data.Maybe (maybe)

consolidate :: [String] -> [(String, Maybe Int)] -> [(String, Maybe Int)]
consolidate temp l = map (\cs -> (cs, M.lookup cs $ fromList' l)) temp

fromList' :: Ord a => [(a, Maybe b)] -> M.Map a b
fromList' xs = foldr insertJust M.empty xs

insertJust :: Ord a => (a, Maybe b) -> M.Map a b -> M.Map a b
insertJust (xs, maybeVal) mp = maybe mp (\n -> M.insert xs n mp) maybeVal

In GHCi:

λ> let myTemplate = ["hello", "world", "this", "is", "a", "test"]
λ> let myList = [("hello", Just 1), ("world", Just 2), ("test", Just 3)]
λ> consolidate myTemplate myList 
[("hello",Just 1),("world",Just 2),("this",Nothing),("is",Nothing),("a",Nothing),("test",Just 3)]