From 738b4042c0b29cc7519ca2ba183118cdc6ad5be3 Mon Sep 17 00:00:00 2001 From: rbasso Date: Wed, 19 Oct 2016 22:11:18 +0900 Subject: [PATCH] word-count: Generalize test suite. - Generalize the test suite to accept multiple signatures. - Change the test suite to be case insensitive. - Add `HINTS.md` explaining that the test suite is flexible. - Add two example solutions: one very simple; the other with a custom type. - Add the simplest type signature to the stub solution. - Remove unneeded dependencies from `package.yaml`. - Remove old example solution. --- exercises/word-count/HINTS.md | 21 +++++++++++++ .../examples/success-newtype/WordCount.hs | 31 +++++++++++++++++++ .../examples/success-newtype/package.yaml | 21 +++++++++++++ .../examples/success-simple/WordCount.hs | 13 ++++++++ .../examples/success-simple/package.yaml | 20 ++++++++++++ exercises/word-count/package.yaml | 2 -- exercises/word-count/src/Example.hs | 8 ----- exercises/word-count/src/WordCount.hs | 3 +- exercises/word-count/test/Tests.hs | 21 +++++++++---- 9 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 exercises/word-count/HINTS.md create mode 100644 exercises/word-count/examples/success-newtype/WordCount.hs create mode 100644 exercises/word-count/examples/success-newtype/package.yaml create mode 100644 exercises/word-count/examples/success-simple/WordCount.hs create mode 100644 exercises/word-count/examples/success-simple/package.yaml delete mode 100644 exercises/word-count/src/Example.hs diff --git a/exercises/word-count/HINTS.md b/exercises/word-count/HINTS.md new file mode 100644 index 000000000..cd5e0bc86 --- /dev/null +++ b/exercises/word-count/HINTS.md @@ -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. diff --git a/exercises/word-count/examples/success-newtype/WordCount.hs b/exercises/word-count/examples/success-newtype/WordCount.hs new file mode 100644 index 000000000..6640269cc --- /dev/null +++ b/exercises/word-count/examples/success-newtype/WordCount.hs @@ -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 diff --git a/exercises/word-count/examples/success-newtype/package.yaml b/exercises/word-count/examples/success-newtype/package.yaml new file mode 100644 index 000000000..34b9adf46 --- /dev/null +++ b/exercises/word-count/examples/success-newtype/package.yaml @@ -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 diff --git a/exercises/word-count/examples/success-simple/WordCount.hs b/exercises/word-count/examples/success-simple/WordCount.hs new file mode 100644 index 000000000..9c79b8d25 --- /dev/null +++ b/exercises/word-count/examples/success-simple/WordCount.hs @@ -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) diff --git a/exercises/word-count/examples/success-simple/package.yaml b/exercises/word-count/examples/success-simple/package.yaml new file mode 100644 index 000000000..bac8ecd0b --- /dev/null +++ b/exercises/word-count/examples/success-simple/package.yaml @@ -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 diff --git a/exercises/word-count/package.yaml b/exercises/word-count/package.yaml index 57a9dbf85..f14977c1f 100644 --- a/exercises/word-count/package.yaml +++ b/exercises/word-count/package.yaml @@ -2,7 +2,6 @@ name: word-count dependencies: - base - - containers library: exposed-modules: WordCount @@ -10,7 +9,6 @@ library: dependencies: # - foo # List here the packages you # - bar # want to use in your solution. - - split tests: test: diff --git a/exercises/word-count/src/Example.hs b/exercises/word-count/src/Example.hs deleted file mode 100644 index 89a80b1d9..000000000 --- a/exercises/word-count/src/Example.hs +++ /dev/null @@ -1,8 +0,0 @@ -module WordCount (wordCount) where -import Data.Char (toLower, isAlphaNum) -import Data.Map.Strict (Map, fromListWith) -import Data.List.Split (wordsBy) - -wordCount :: String -> Map String Int -wordCount = fromListWith (+) . map pair . wordsBy (not . isAlphaNum) - where pair word = (map toLower word, 1) \ No newline at end of file diff --git a/exercises/word-count/src/WordCount.hs b/exercises/word-count/src/WordCount.hs index af6109af7..07db8f29d 100644 --- a/exercises/word-count/src/WordCount.hs +++ b/exercises/word-count/src/WordCount.hs @@ -1,3 +1,4 @@ module WordCount (wordCount) where -wordCount = undefined +wordCount :: String -> [(String, Int)] +wordCount xs = undefined diff --git a/exercises/word-count/test/Tests.hs b/exercises/word-count/test/Tests.hs index 0561cd2b2..862a7b886 100644 --- a/exercises/word-count/test/Tests.hs +++ b/exercises/word-count/test/Tests.hs @@ -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) @@ -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.