简体   繁体   中英

How can I check string in a (list of ( pair of string and (list of string)))?

I have a data called "Sample", defined as

data Sample = Test1 (String,[[String]])
             | Test2 (String,[[String]])

Then I create a list[Sample] called "Samples", say

[Test1 ("Sample1",[["works","running"]]), Test2 ("Sample2", []),
 Test1 ("Sample3", [["aborts"]] ]

Now, I need to check whether "aborts" is present under "Sample3". I have a basic idea of how to do it but I am not sure how to implement it in Haskell.

My idea is

check:: String -> [String] -> [Sample] -> Bool
check samplename result samples =

I can call it by:

check "Sample3" ["aborts"] Samples

But how do I implement this function.


I have solved like this but I am looking for a better version.

My version is:

check:: String -> [String] -> [Sample] -> Bool
check samplename result samples = 
  if( "True" `elem` final ) 
  then True 
  else False
    where 
      final = [x | sample <- samples, 
                   x <- [ case sample of
                               Test1 (name,res) = 
                                 if name == samplename && (result `elem` res) 
                                 then "True" 
                                 else "False"
                               Test2 (name, res) = "False"]]

This should do the trick:

data Sample = Test1 String [[String]]
            | Test2 String [[String]]

sampleName :: Sample -> String
sampleName (Test1 name _) = name
sampleName (Test2 name _) = name

getData :: Sample -> [[String]]
getData (Test1 _ samples) = samples
getData (Test2 _ samples) = samples

check :: String -> [String] -> [Sample] -> Bool
check name needle samples = any doCheck samples
  where
    doCheck s = if sampleName s == name
                then needle `elem` getData s
                else False

If all names are supposed to be distinct, you can abort the search faster:

check2 :: String -> [String] -> [Sample] -> Bool
check2 name needle samples = go samples
  where
    go []     = False
    go (s:ss) = if sampleName s == name
                then needle `elem` getData s
                else go ss

Testing:

*Main> check "Sample3" ["aborts"] tst
True

Comments on your version:

data Sample = Test1 (String,[[String]])
             | Test2 (String,[[String]])

You don't need tuples here (see my code).

if( "True" `elem` final ) then True else False

In general, if expr then True else False can be always replaced with expr . And why do you use strings as booleans?

final = [x | sample <- samples,
         x <- [ case sample of
                   Test1 (name,res) -> if name == samplename
                                         && (result `elem` res)
                                      then "True" else "False"
                   Test2 (name, res) -> "False"]]

This can be rewritten without list comprehensions by using any :

check:: String -> [String] -> [Sample] -> Bool
check samplename result samples = any doCheck samples
  where
    doCheck (Test1 n s) = n == samplename && result `elem` s
    doCheck _           = False

If we think about what check must do at a high level, we know it must check for every element of samples to see if the test "Sample3" contains any (or all, i'm unclear which you mean) of the strings result are present.

So we know we need recursion and we can start by making the general outline of the function:

 check :: String -> [String] -> [Sample] -> Bool
 check samplename result []     = False
 check samplename result (x:xs) = ...

So when the list is empty, no matches can occur so we can immediately return false. In the recursive case we know we need to check x and if no match is found, continue checking xs. One possible way to do this is with a helper function check' (you can also just inline the check').

 check samplename result (x:xs) = check' x || check samplename result xs
     where check' ...

ok, so what does check' do? It checks the datatype Sample to see if any matches occur. We know Sample has two constructors, Test1 and Test2 so check' should look like

 check' :: Sample -> Bool
 check' (Test1 (name, values)) = ...
 check' (Test2 (name, values)) = ...

The first thing we have to do is test the value of name to see if it matches samplename . We can do this easily using guards

 check' :: Sample -> Bool
 check' (Test1 (name, values)) | name == samplename = ...
 check' (Test2 (name, values)) | name == samplename = ...
 check' _                                           = False

Since check' is a child function of check, the variables defined in check are in scope, so we can just refer to them. A new case is added to handle the event that the names do not match.

Ok, so now the idea is to check if any (or all) of the values in result occur in values. Luckily the prelude has a function we can use for this.

elem :: Eq a => a -> [a] -> Bool

The function now becomes

 check' :: Sample -> Bool
 check' (Test1 (name, values)) | name == samplename = result `elem` values
 check' (Test2 (name, values)) | name == samplename = result `elem` values
 check' _                                           = False

The full function is thus:

check :: String -> [String] -> [Sample] -> Bool
check samplename result []     = False
check samplename result (x:xs) = check' x || check samplename result xs
   where check' :: Sample -> Bool
         check' (Test1 (name, values)) | name == samplename = result `elem` values
         check' (Test2 (name, values)) | name == samplename = result `elem` values
         check' _                                           = False

Checking every element of a list with a predicate is so common that the prelude has standard functions for this. one possible way to define check is using the functions or and map . This will generally result in a less efficient function though:

check :: String -> [String] -> [Sample] -> Bool
check samplename result samples = or (map check' samples)
   where check' :: Sample -> Bool
         check' (Test1 (name, values)) | name == samplename = result `elem` values
         check' (Test2 (name, values)) | name == samplename = result `elem` values
         check' _                                           = False

The function can further be simplified by adapting an alternative structure for the datatype, such as

type ID     = Int
type Name   = String
type Values = [[String]]
data Sample = Test ID Name Values

The function then becomes

check :: String -> [String] -> [Sample] -> Bool
check samplename result samples = or (map check' samples)
   where check' :: Sample -> Bool
         check' (Test _ name values) | name == samplename = result `elem` values
         check' _                                         = False

Lastly since the result of check' is a Bool, and guards are Bool as well, we can refactor check' to a simpler form which doesn't need the fallthrough case

check :: String -> [String] -> [Sample] -> Bool
check samplename result samples = or (map check' samples)
   where check' :: Sample -> Bool
         check' (Test _ name values) = name == samplename && result `elem` values

This ** or (map .. ..)** pattern is so common that again there's a prelude function any which does this. check can then be further simplified to

check :: String -> [String] -> [Sample] -> Bool
check samplename result samples = any check' samples
   where check' :: Sample -> Bool
         check' (Test _ name values) = name == samplename && result `elem` values

Here is my version of the solution. But I dont think your data represent any real problem , you can easily use Map if you want to store something like [Sample] which is basically a list of key value pairs. Well the idea behind my solution is also inspired from Map . I have written a lookup' function similar to lookup which given a key returns the value . writing the rest function is trivial then. Similar to your approach but less messy .

data Sample = Test1 (String,[[String]])
             | Test2 (String,[[String]])
        deriving Show
samples = [ Test1 ("Sample1",[["works","running"]]), Test2 ("Sample2", []), Test1    ("Sample3", [["aborts"]]) ]

fromSample :: Sample -> (String,[[String]])
fromSample (Test1 x) = x
fromSample (Test2 x) = x

lookup' :: String -> [Sample] -> Maybe [[String]]
lookup' str [] = Nothing
lookup' str (x:xs) | fst pair == str = Just $ snd pair
               | otherwise = lookup' str xs
            where pair = fromSample x

check :: String -> [String] -> [Sample] -> Bool
check sample str xs = case lookup' sample xs of
                        Nothing -> False 
                        Just list -> str `elem` list 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM