Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions exercises/word-count/HINTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Hints

To complete this exercise you need to implement the function `wordCount`,
that takes a *text* and returns how many times each *word* appears.

If it is your first time solving this exercise, it is recommended that you
stick to the provided signature:

```haskell
wordCount :: String -> [(String, Int)]
```

Later, it may be a good idea to revisit this problem and play with other data
types and libraries:

- `Text`, from package *text*.
- `Map`, from package *containers*.
- `MultiSet`, from package *multiset*

The test suite was intentionally designed to accept almost any type signature
that makes sense, so you are encouraged to find the one you think is the best.
31 changes: 31 additions & 0 deletions exercises/word-count/examples/success-newtype/WordCount.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{-# LANGUAGE TypeFamilies #-}

module WordCount (wordCount) where

import Prelude hiding (null)
import Data.Char (isAlphaNum)
import Data.Text (Text, null, split, toLower)
import Data.MultiSet (MultiSet, Occur, fromList, fromOccurList, toOccurList)

import qualified GHC.Exts (IsList(..))

wordCount :: Text -> Bag Text
wordCount = Bag
. fromList
. map toLower
. wordsBy (not . isAlphaNum)

-- The `text` package misses this function that
-- exists in package `split`, but works on lists.
wordsBy :: (Char -> Bool) -> Text -> [Text]
wordsBy p = filter (not . null) . split p

-- MultiSet is not an instance of `IsList`, so we create
-- a newtype to wrap it, avoiding an orphan instance.
newtype Bag a = Bag { toMultiSet :: MultiSet a }

instance (Ord a) => GHC.Exts.IsList (Bag a)
where
type Item (Bag a) = (a, Occur)
fromList = Bag . fromOccurList
toList = toOccurList . toMultiSet
21 changes: 21 additions & 0 deletions exercises/word-count/examples/success-newtype/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: word-count

dependencies:
- base

library:
exposed-modules: WordCount
source-dirs: src
dependencies:
# - foo # List here the packages you
# - bar # want to use in your solution.
- multiset
- text

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- word-count
- hspec
13 changes: 13 additions & 0 deletions exercises/word-count/examples/success-simple/WordCount.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module WordCount (wordCount) where

import Control.Arrow ((&&&))
import Data.Char (toLower, isAlphaNum)
import Data.List (group, sort)
import Data.List.Split (wordsBy)

wordCount :: String -> [(String, Int)]
wordCount = map (head &&& length)
. group
. sort
. map (map toLower)
. wordsBy (not . isAlphaNum)
20 changes: 20 additions & 0 deletions exercises/word-count/examples/success-simple/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: word-count

dependencies:
- base

library:
exposed-modules: WordCount
source-dirs: src
dependencies:
# - foo # List here the packages you
# - bar # want to use in your solution.
- split

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- word-count
- hspec
2 changes: 0 additions & 2 deletions exercises/word-count/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ name: word-count

dependencies:
- base
- containers

library:
exposed-modules: WordCount
source-dirs: src
dependencies:
# - foo # List here the packages you
# - bar # want to use in your solution.
- split

tests:
test:
Expand Down
8 changes: 0 additions & 8 deletions exercises/word-count/src/Example.hs

This file was deleted.

3 changes: 2 additions & 1 deletion exercises/word-count/src/WordCount.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module WordCount (wordCount) where

wordCount = undefined
wordCount :: String -> [(String, Int)]
wordCount xs = undefined
21 changes: 15 additions & 6 deletions exercises/word-count/test/Tests.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE RecordWildCards #-}

import Data.Bifunctor (bimap)
import Data.Char (toLower)
import Data.Foldable (for_)
import Data.Map (fromList)
import Test.Hspec (Spec, describe, it, shouldBe)
import GHC.Exts (fromList, toList)
import Test.Hspec (Spec, describe, it, shouldMatchList)
import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith)

import WordCount (wordCount)
Expand All @@ -15,11 +17,18 @@ specs :: Spec
specs = describe "word-count" $
describe "wordCount" $ for_ cases test
where

test Case{..} = it description $ returnedMap `shouldBe` expectedMap
-- Here we used `fromIntegral`, `fromList` and `toList` to generalize
-- the tests, accepting any function that receives a string-like argumment
-- and returns a type that can be converted to [(String, Integer)].
-- Also, the words are lower-cased before comparison and the output's
-- order is ignored.
test Case{..} = it description $ expression `shouldMatchList` expected
where
returnedMap = wordCount input
expectedMap = fromIntegral <$> fromList expected
expression = map (bimap (map toLower . toList) fromIntegral)
. toList
. wordCount
. fromList
$ input

-- Test cases adapted from `exercism/x-common/word-count.json` on 2016-07-26.

Expand Down