Removing duplicates from list and filtering non-letter characters
Think carefully about what data structure you're using, lists aren't always the best choice. In this case you need to do two major things with your words.


Associate words with the line number they first appear on.
Remove words from one group using entries from another.


Right off the bat whenever you need to associate one set of elements with another, you should think of a map. In Haskell, you'll want to look at the documentation for Data.Map.

By reordering and reconsidering the operations you're performing on lists and sets, you should be able to rewrite your code to use maps and significantly cut down the amount of code you have written and end up with something more expressive too.

As it isn't clear that you've actually turned in this assignment I'm afraid I'll have to be coy, but here's the most pointed help I can give you on your specific questions.

Regarding removing duplicate words, the trick is to rely on the fact that maps can only contain a given key once. Pay close attention to the various ways of constructing a list that are specified in the documentation for Data.Map.

Filtering out the non-letters in a string doesn't get much simpler than filter Char.isLetter in your code above. The dissatisfaction you are feeling might be because of the gross duplication between the definitions of word and stopWord, and word' and stopWord'. Any time you have two lines in Haskell that are identical but for the value you're operating on, your code is crying out for a new function definition.

Your code is lingering in IO far more than is necessary, pull the pure data manipulating machinery out into separate functions. To start you off, knowing no more than the type signatures I've given here, what do you think these functions do? What part of your code do they replace? Take a shot at defining them.

normalize :: String -&gt; String
??? :: [a] -&gt; [(a, Int)]
??? :: String -&gt; [([String], Int)]