3

I have a string containing letters I want to be sure are in the words in the list. Running it however results in it still leaving behind words that contain the undesired letters.

Here's my function:

import Data.List    

filterWords :: String -> [String]
filterWords str =
  let strs      = words str
      letters   = concat . words . nub $ "poultry outwits ants"
      predicate = dropWhile (`elem` letters) ['a' .. 'z']
  in  dropWhile (any (`elem` predicate)) strs

What do I need to change to make this work?

To make it clear, I want to filter away any words that contain letters not in "poultry outwits ants", meaning a word like "years" would be dropped because despite containing 'y', 'a', 'r', and 's' which all satisfy the predicate, it also contains 'e' which doesn't.

4
  • I don't understand the task - you want to drop all those words which contain none of the letters in "poultry outwits ants"? Commented Feb 18, 2015 at 9:07
  • @FrerichRaabe that contain none of the letters yes, but I mean extended to something like "years" being filtered away as well because it contains 'e' Commented Feb 18, 2015 at 9:07
  • So you really want letters :: Data.Set Char, not a list? Commented Feb 18, 2015 at 9:08
  • It's funny, coming back to this five years later, and I honestly don't remember ever making this post or what the original context for this was.... Commented Aug 14, 2020 at 9:02

1 Answer 1

4

A good way to filter a list of things (e.g. words) is to use the filter function. What you need to provide is a predicate which tells whether a string should be included or not. You commented that you want to include those strings which consists of letters in "poultry outwits ants", so that would be

filterWords :: String -> [String]
filterWords str = filter acceptableWord (words str)
  where
    acceptableWord = all (`elem` "poultry outwits ants")

Now, in another comment you wrote that

Some of the words I get have more copies of the same letter than there are in the original.

So I suspect what you really want is to figure out which words can be formed out of the letters in "poultry outwits ants".

To do this, you could count how often each character appears in the given word (and in the mgic string poultry outwits ants) and then verify that not only each letter in the word appears in the magic string but also that the letter doesn't appear more often than in the magic string.

I'd start by defining a function which calculates 'character frequency table', i.e. it counts how often each character occurs in the given string:

freq :: String -> [(Char, Int)]
freq = map (\s -> (head s, length s)) . group . sort

Furthermore, I'd define a function which tells whether one frequency table x is a "subset" of another table y, i.e. it verifies that each character in x also appears in y, but it doesn't occur more often:

subset :: [(Char, Int)] -> [(Char, Int)] -> Bool
subset x y = all f x
  where
    f (ch, occ) = case lookup ch y of
                      Just occ' -> occ <= occ'
                      Nothing   -> False

You can then use this to define acceptableWord such that it only accepts words whose frequency table is a subset of the frequency table of the magic string, so we get:

filterWords :: String -> [String]
filterWords str = filter acceptableWord (words str)
  where
    acceptableWord w = subset (freq w) (freq "poultry outwits ants")
Sign up to request clarification or add additional context in comments.

6 Comments

I tried to think of a way to use filter, but couldn't wrap my head around it... Guess the solution was too simple... Thanks!
It you want to be a Haskell hipster you could make it point-free, shortening the definition to filterWords = filter (all (`elem` "poultry outwits ants")) . words. Actually, now that I see it - it still looks fairly comprehensible IMHO!
I actually did... Now to filter away stuff that doesn't fit the number of letters...
What does doesn't fit the number of letters mean?
Some of the words I get have more copies of the same letter than there are in the original
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.