Skip to content

dibs: I will implement exercise run-length-encoding #461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 30, 2016
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
6 changes: 6 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
"topics": [
]
},
{
"slug": "run-length-encoding",
"difficulty": 1,
"topics": [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what is the best position for this exercise, so I think it is ok here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends. As it stands this is a very simple exercise, but if some of the more complicated error-handling and number-handling ideas (here exercism/problem-specifications#238) are implemented it would no longer be simple. I put it before rna-transcription because at the moment it only requires simple types and not Maybe for error handling.

]
},
{
"slug": "rna-transcription",
"difficulty": 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: run-length-encoding

dependencies:
- base

library:
exposed-modules: RunLength
source-dirs: src

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- run-length-encoding
- hspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module RunLength (decode, encode) where

import Data.Char (isDigit)

decode :: String -> String
decode "" = ""
decode s = replicate n c ++ decode rest
where
i = takeWhile isDigit s
n = if null i then 1 else read i
c = head $ filter (not . isDigit) s
rest = drop (1 + length i) s

encode :: String -> String
encode "" = ""
encode s = i ++ c : encode rest
where
c = head s
l = length $ takeWhile (== c) s
i = if l == 1 then "" else show l
rest = drop l s
19 changes: 19 additions & 0 deletions exercises/run-length-encoding/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: run-length-encoding

dependencies:
- base

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

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- run-length-encoding
- hspec
7 changes: 7 additions & 0 deletions exercises/run-length-encoding/src/RunLength.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module RunLength (decode, encode) where

decode :: String -> String
decode = undefined

encode :: String -> String
encode = undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perfect the way it is and also matches exactly what we did in all the other exercises.

Recently, we discussed in #459 that it would be nice to use error to give better messages than undefined, so an alternative would be:

encode xs  = error "You need to implement this function."

No change is needed here, and we are not yet in the processes of rewriting the stub solutions. Your choice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If at a later date the decision is made to replace undefined with error (a decision that I'd support) the change can easily be implemented with a single find-replace.

1 change: 1 addition & 0 deletions exercises/run-length-encoding/stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resolver: lts-7.9
75 changes: 75 additions & 0 deletions exercises/run-length-encoding/test/Tests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{-# LANGUAGE RecordWildCards #-}

import Data.Foldable (for_)
import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith)

import RunLength (encode, decode)

main :: IO ()
main = hspecWith defaultConfig {configFastFail = True} specs

specs :: Spec
specs = describe "run-length-encoding" $ do
describe "decode" $ for_ decodeCases $ test decode
describe "encode" $ for_ encodeCases $ test encode
describe "both" $ for_ bothCases $ test (decode . encode)
where
test f Case{..} = it description $ f input `shouldBe` expected

-- Test cases adapted from file
-- `exercism/x-common/exercises/run-length-encoding/canonical-data.json`
-- on 2016-12-26.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test suite could be better "translated" as a big do block, reproducing the exact test ordering from x-common.

That said, the cases ordering doesn't seem to be so important in this exercise, so I think it is great the way it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a conscious decision to change the order of the tests. I like having tests for different methods separated so that I can fully implement decode and test it before I work on encode. I prefer this to testing that both encode and decode work for the simplest case, and then another simple case, etc. I feel this workflow is nicer for beginners, especially since we stop the test on first failure by default. On the other hand, it's a pain to manually reorder things. I assume that in the long run the exercism project will implement an automated system for generating the tests from the common json files though that's another story.


data Case = Case { description :: String
, input :: String
, expected :: String
}

encodeCases :: [Case]
encodeCases =
[ Case { description = "encode empty string"
, input = ""
, expected = ""
}
, Case { description = "encode single characters only"
, input = "XYZ"
, expected = "XYZ"
}
, Case { description = "encode simple"
, input = "AABBBCCCC"
, expected = "2A3B4C"
}
, Case { description = "encode with single values"
, input = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"
, expected = "12WB12W3B24WB"
}
]

decodeCases :: [Case]
decodeCases =
[ Case { description = "decode empty string"
, input = ""
, expected = ""
}
, Case { description = "decode single characters only"
, input = "XYZ"
, expected = "XYZ"
}
, Case { description = "decode simple"
, input = "2A3B4C"
, expected = "AABBBCCCC"
}
, Case { description = "decode with single values"
, input = "12WB12W3B24WB"
, expected = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"
}
]

bothCases :: [Case]
bothCases =
[ Case { description = "decode . encode combination"
, input = "zzz ZZ zZ"
, expected = "zzz ZZ zZ"
}
]