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
31 changes: 8 additions & 23 deletions exercises/luhn/examples/success-standard/src/Luhn.hs
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
module Luhn (checkDigit, addends, checksum, isValid, create) where
module Luhn (isValid) where

revDigits :: Integral a => a -> [a]
revDigits n = rem10 : digits
where (quot10, rem10) = n `quotRem` 10
digits | quot10 == 0 = []
| otherwise = revDigits quot10
import Data.Char (digitToInt)

luhnDouble :: Integral a => a -> a
luhnDouble n | n < 5 = n * 2
| otherwise = n * 2 - 9

luhnDigits :: Integral a => a -> [a]
luhnDigits = zipWith ($) (cycle [id, luhnDouble]) . revDigits
luhnDigits :: Integral a => [a] -> [a]
luhnDigits = zipWith ($) (cycle [id, luhnDouble]) . reverse

checkDigit :: Integral a => a -> a
checkDigit = head . revDigits

addends :: Integral a => a -> [a]
addends = reverse . luhnDigits

checksum :: Integral a => a -> a
checksum :: Integral a => [a] -> a
checksum = (`rem` 10) . sum . luhnDigits

isValid :: Integral a => a -> Bool
isValid = (0 ==) . checksum

create :: Integral a => a -> a
create n | chk == 0 = n10
| otherwise = n10 + (10 - chk)
where n10 = n * 10
chk = checksum n10 `rem` 10
isValid :: String -> Bool
isValid s = length digits > 1 && checksum digits == 0
where digits = map digitToInt $ filter (/= ' ') s
2 changes: 1 addition & 1 deletion exercises/luhn/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: luhn
version: 0.9.0.1 # 2016-08-04
version: 1.0.0.2

dependencies:
- base
Expand Down
16 changes: 2 additions & 14 deletions exercises/luhn/src/Luhn.hs
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
module Luhn (addends, checkDigit, checksum, create, isValid) where
module Luhn (isValid) where

addends :: Integer -> [Integer]
addends n = error "You need to implement this function."

checkDigit :: Integer -> Integer
checkDigit n = error "You need to implement this function."

checksum :: Integer -> Integer
checksum n = error "You need to implement this function."

create :: Integer -> Integer
create n = error "You need to implement this function."

isValid :: Integer -> Bool
isValid :: String -> Bool
isValid n = error "You need to implement this function."
138 changes: 68 additions & 70 deletions exercises/luhn/test/Tests.hs
Original file line number Diff line number Diff line change
@@ -1,79 +1,77 @@
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE RecordWildCards #-}

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

import Luhn (addends, checkDigit, checksum, create, isValid)
import Luhn (isValid)

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

specs :: Spec
specs = describe "luhn" $ do
describe "standard tests" $ do

it "check digit" $
checkDigit 34567 `shouldBe` 7

it "check digit with input ending in zero" $
checkDigit 91370 `shouldBe` 0

it "check addends" $
addends 12121 `shouldBe` [1, 4, 1, 4, 1]

it "check too large addends" $
addends 8631 `shouldBe` [7, 6, 6, 1]

-- The reference test cases expect the checksum function to return
-- the simple sum of the transformed digits, not their `mod 10` sum.
-- In this track, we insist on the `mod 10`. :)

it "checksum" $
checksum 4913 `shouldBe` 2 -- The reference test expects 22.

it "checksum of larger number" $
checksum 201773 `shouldBe` 1 -- The reference test expects 21.

it "check invalid number" $
isValid 738 `shouldBe` False

it "check valid number" $
isValid 8739567 `shouldBe` True

it "create valid number" $
create 123 `shouldBe` 1230

it "create larger valid number" $
create 873956 `shouldBe` 8739567

it "create even larger valid number" $
create 837263756 `shouldBe` 8372637564

describe "track-specific tests" $ do

-- This track has some tests that were not included in the
-- reference test cases from `exercism/x-common/leap.json`.

it "checksum 1111" $
checksum 1111 `shouldBe` 6

it "checksum 8763" $
checksum 8763 `shouldBe` 0

it "checksum 8739567" $
checksum 8739567 `shouldBe` 0

it "checksum 2323200577663554" $
checksum 2323200577663554 `shouldBe` 0

it "isValid 1111" $
isValid 1111 `shouldBe` False

it "isValid 8763" $
isValid 8763 `shouldBe` True

it "isValid 2323200577663554" $
isValid 2323200577663554 `shouldBe` True

it "create 232320057766355" $
create 232320057766355 `shouldBe` 2323200577663554
specs = describe "valid" $ for_ cases test
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

notice! no luhn!

where
test Case{..} = it description $ isValid input `shouldBe` expected

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

cases :: [Case]
cases = [ Case { description = "single digit strings can not be valid"
, input = "1"
, expected = False
}
, Case { description = "A single zero is invalid"
, input = "0"
, expected = False
}
, Case { description = "a simple valid SIN that remains valid if reversed"
, input = "059"
, expected = True
}
, Case { description = "a simple valid SIN that becomes invalid if reversed"
, input = "59"
, expected = True
}
, Case { description = "a valid Canadian SIN"
, input = "055 444 285"
, expected = True
}
, Case { description = "invalid Canadian SIN"
, input = "055 444 286"
, expected = False
}
, Case { description = "invalid credit card"
, input = "8273 1232 7352 0569"
, expected = False
}
-- This track is not testing these cases, since we would rather focus on the algorithm,
-- and because it seems strange to be unable to distinguish between well-formed invalid input and malformed input.
-- , Case { description = "valid strings with a non-digit included become invalid"
-- , input = "055a 444 285"
-- , expected = False
-- }
-- , Case { description = "valid strings with punctuation included become invalid"
-- , input = "055-444-285"
-- , expected = False
-- }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here it seems that you suppressed one test:

    {
      "description": "valid strings with symbols included become invalid",
      "property": "valid",
      "input": "055£ 444$ 285",
      "expected": false
    },

Was it intentional?

I don't consider that a problem, because considering what was discussed in exercism/problem-specifications#428, it probably shouldn't exist anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh! I guess I'll add it (and comment it) for the purpose of following x-common, but you are right that we expect the case to go away soon.

-- , Case { description = "valid strings with symbols included become invalid"
-- , input = "055£ 444$ 285"
-- , expected = False
-- }
, Case { description = "single zero with space is invalid"
, input = " 0"
, expected = False
}
, Case { description = "more than a single zero is valid"
, input = "0000 0"
, expected = True
}
, Case { description = "input digit 9 is correctly converted to output digit 9"
, input = "091"
, expected = True
}
]