diff --git a/exercises/crypto-square/src/CryptoSquare.hs b/exercises/crypto-square/src/CryptoSquare.hs index 49c6b055d..5b4222694 100644 --- a/exercises/crypto-square/src/CryptoSquare.hs +++ b/exercises/crypto-square/src/CryptoSquare.hs @@ -1,18 +1,4 @@ -module CryptoSquare - ( ciphertext - , normalizeCiphertext - , normalizePlaintext - , plaintextSegments - ) where +module CryptoSquare (encode) where -ciphertext :: String -> String -ciphertext = undefined - -normalizeCiphertext :: String -> String -normalizeCiphertext = undefined - -normalizePlaintext :: String -> String -normalizePlaintext = undefined - -plaintextSegments :: String -> [String] -plaintextSegments = undefined +encode :: String -> String +encode = undefined diff --git a/exercises/crypto-square/src/Example.hs b/exercises/crypto-square/src/Example.hs index dcd33f09c..91c175f0c 100644 --- a/exercises/crypto-square/src/Example.hs +++ b/exercises/crypto-square/src/Example.hs @@ -1,23 +1,15 @@ -module CryptoSquare ( normalizePlaintext - , plaintextSegments - , ciphertext - , normalizeCiphertext ) where +module CryptoSquare (encode) where -import Data.Char (isAlphaNum, toLower) -import Data.List (transpose) +import Data.Char (isAlphaNum, toLower) +import Data.List (transpose) import Data.List.Split (chunksOf) -squareSize :: String -> Int -squareSize = ceiling . (sqrt :: Double -> Double) . fromIntegral . length - -normalizePlaintext :: String -> String -normalizePlaintext = map toLower . filter isAlphaNum - -plaintextSegments :: String -> [String] -plaintextSegments = (squareSize >>= chunksOf) . normalizePlaintext - -ciphertext :: String -> String -ciphertext = concat . transpose . plaintextSegments - -normalizeCiphertext :: String -> String -normalizeCiphertext = unwords . transpose . plaintextSegments +encode :: String -> String +encode = unwords + . transpose + . (squareSize >>= chunksOf) + . map toLower + . filter isAlphaNum + where + squareSize :: String -> Int + squareSize = ceiling . (sqrt :: Double -> Double) . fromIntegral . length diff --git a/exercises/crypto-square/test/Tests.hs b/exercises/crypto-square/test/Tests.hs index 25f9faaf6..3d3ac7b85 100644 --- a/exercises/crypto-square/test/Tests.hs +++ b/exercises/crypto-square/test/Tests.hs @@ -1,93 +1,53 @@ -import Test.Hspec (Spec, describe, it, shouldBe) +{-# LANGUAGE RecordWildCards #-} + +import Data.Char (isSpace) +import Data.Foldable (for_) +import Data.Function (on) +import Test.Hspec (Spec, describe, it, shouldBe, shouldMatchList) import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith) -import CryptoSquare - ( ciphertext - , normalizeCiphertext - , normalizePlaintext - , plaintextSegments - ) +import CryptoSquare (encode) main :: IO () main = hspecWith defaultConfig {configFastFail = True} specs specs :: Spec -specs = describe "crypto-square" $ do - - -- Test cases adapted from `exercism/x-common/crypto-square.json` - -- on 2016-08-02. Some deviations exist and are noted in comments. - - describe "normalizePlaintext" $ do - - it "Lowercase" $ - normalizePlaintext "Hello" - `shouldBe` "hello" - - it "Remove spaces" $ - normalizePlaintext "Hi there" - `shouldBe` "hithere" - - it "Remove punctuation" $ - normalizePlaintext "@1, 2%, 3 Go!" - `shouldBe` "123go" - - describe "plaintextSegments" $ do - - it "empty plaintext results in an empty rectangle" $ - plaintextSegments "" - `shouldBe` [] - - it "4 character plaintext results in an 2x2 rectangle" $ - plaintextSegments "Ab Cd" - `shouldBe` [ "ab" - , "cd" ] - - it "9 character plaintext results in an 3x3 rectangle" $ - plaintextSegments "This is fun!" - `shouldBe` [ "thi" - , "sis" - , "fun" ] - - it "54 character plaintext results in an 8x7 rectangle" $ - plaintextSegments "If man was meant to stay on the ground, god would have given us roots." - `shouldBe` [ "ifmanwas" - , "meanttos" - , "tayonthe" - , "groundgo" - , "dwouldha" - , "vegivenu" - , "sroots" ] - - describe "ciphertext" $ do - - -- The function described by the reference file in `x-common` - -- as `encoded` is called `ciphertext` in this track. - - it "empty plaintext results in an empty encode" $ - ciphertext "" - `shouldBe` "" - - it "Non-empty plaintext results in the combined plaintext segments" $ - ciphertext "If man was meant to stay on the ground, god would have given us roots." - `shouldBe` "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" - - describe "normalizeCiphertext" $ do - - -- The function described by the reference file in `x-common` - -- as `ciphertext` is called `normalizeCiphertext` in this track. - - it "empty plaintext results in an empty ciphertext" $ - normalizeCiphertext "" - `shouldBe` "" - - it "9 character plaintext results in 3 chunks of 3 characters" $ - normalizeCiphertext "This is fun!" - `shouldBe` "tsf hiu isn" - - {- In this track the encoded text chunks are not padded with spaces. - - it "54 character plaintext results in 7 chunks, the last two padded with spaces" $ - normalizeCiphertext "If man was meant to stay on the ground, god would have given us roots." - `shouldBe` "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " - - -} +specs = describe "crypto-square" $ + describe "encode" $ for_ cases test + where + + test Case{..} = describe description $ do + + let shouldMatchWords = shouldBe `on` words + shouldMatchString = shouldBe `on` filter (not . isSpace) + shouldMatchChars = shouldMatchList `on` filter (not . isSpace) + + it "normalizes the input" $ encode input `shouldMatchChars` expected + it "reorders the characters" $ encode input `shouldMatchString` expected + it "groups the output" $ encode input `shouldMatchWords` expected + +-- Test cases created from scratch on 2016-10-05, diverging from `x-common`. + +data Case = Case { description :: String + , input :: String + , expected :: String + } + +cases :: [Case] +cases = [ Case { description = "perfect square, all lowercase with space" + , input = "a dog" + , expected = "ao dg" + } + , Case { description = "perfect rectangle, mixed case" + , input = "A camel" + , expected = "am ce al" + } + , Case { description = "incomplete square with punctuation" + , input = "Wait, fox!" + , expected = "wtx af io" + } + , Case { description = "incomplete rectangle with symbols" + , input = "cat | cut -d@ -f1 | sort | uniq" + , expected = "ctoi adrq tft c1u usn" + } + ]