checksum (empty) → 0.0
raw patch · 8 files changed
+291/−0 lines, 8 filesdep +basedep +explicit-exceptiondep +utility-htsetup-changed
Dependencies added: base, explicit-exception, utility-ht
Files
- LICENSE +30/−0
- Setup.lhs +3/−0
- checksum.cabal +39/−0
- src/Math/Checksum/CitizenCard/Germany.hs +36/−0
- src/Math/Checksum/EAN.hs +47/−0
- src/Math/Checksum/IBAN.hs +65/−0
- src/Math/Checksum/ISBN.hs +46/−0
- src/Math/Checksum/Utility.hs +25/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, Henning Thielemann++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Henning Thielemann nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ checksum.cabal view
@@ -0,0 +1,39 @@+Name: checksum+Version: 0.0+Synopsis: Compute and verify checksums of ISBN, IBAN, etc.+Description:+ Compute and verify checksums of various identifiers,+ such as IBAN, ISBN, EAN, Germany's Citizen Card.+Homepage: http://hub.darcs.net/thielema/checksum+License: BSD3+License-File: LICENSE+Author: Henning Thielemann+Maintainer: haskell@henning-thielemann.de+Category: Math+Build-Type: Simple+Cabal-Version: >=1.10++Source-Repository this+ Tag: 0.0+ Type: darcs+ Location: http://hub.darcs.net/thielema/checksum++Source-Repository head+ Type: darcs+ Location: http://hub.darcs.net/thielema/checksum++Library+ Exposed-Modules:+ Math.Checksum.CitizenCard.Germany+ Math.Checksum.IBAN+ Math.Checksum.ISBN+ Math.Checksum.EAN+ Other-Modules:+ Math.Checksum.Utility+ Build-Depends:+ explicit-exception >= 0.1.9 && <0.2,+ utility-ht >=0.0.11 && <0.1,+ base >=4.5 && <5+ Hs-Source-Dirs: src+ Default-Language: Haskell2010+ GHC-Options: -Wall
+ src/Math/Checksum/CitizenCard/Germany.hs view
@@ -0,0 +1,36 @@+{- |+Personalausweisnummer++<https://de.wikipedia.org/wiki/Ausweisnummer>+-}+module Math.Checksum.CitizenCard.Germany (construct) where++import qualified Data.List.HT as ListHT+import Math.Checksum.Utility (decomposePositional)+++digits :: Int -> Int -> [Int]+digits n x = reverse $ ListHT.padRight 0 n $ decomposePositional 10 x++check3 :: [Int] -> Int+check3 = flip mod 10 . sum . zipWith (*) (cycle [7,3,1])++addCheck3 :: [Int] -> [Int]+addCheck3 xs = xs ++ [check3 xs]++string :: [Int] -> String+string = concatMap show++{- |+> construct city no birthDate expiration+> construct 1234 56789 980706 180706 == "1234567897D<<9807062<1807066<<<<<<<8"+-}+construct :: Int -> Int -> Int -> Int -> String+construct city no birth expire =+ let dno = addCheck3 $ digits 4 city ++ digits 5 no+ dbirth = addCheck3 $ digits 6 birth+ dexpire = addCheck3 $ digits 6 expire+ in string dno ++ "D<<" +++ string dbirth ++ "<" +++ string dexpire ++ replicate 7 '<' +++ show (check3 $ dno ++ dbirth ++ dexpire)
+ src/Math/Checksum/EAN.hs view
@@ -0,0 +1,47 @@+{- |+European Article Number++<https://en.wikipedia.org/wiki/International_Article_Number>+-}+module Math.Checksum.EAN (checksum, valid) where++import qualified Math.Checksum.Utility as Util++import Control.Monad.Exception.Synchronous (Exceptional)++import qualified Data.List as List+++{- |+> checksum "400638129240" == Success 5+-}+checksum :: String -> Exceptional String Int+checksum xs = do+ ws <- mapM Util.intFromDigit xs+ return $ 9 - remainder (ws++[9])++{- |+> valid "4006381292405" == Nothing+> valid "4006381292406" == Just "check sum does not match"+-}+valid :: String -> Maybe String+valid xs = Util.processValid $ do+ ws <- mapM Util.intFromDigit xs+ return (0 == remainder ws)++{-+Not stream-friendly way, but more declarative.+-}+_remainder :: [Int] -> Int+_remainder ws = mod (sum $ zipWith (*) (cycle [1,3]) (reverse ws)) 10++remainder :: [Int] -> Int+remainder ws =+ let (x,y) = sum2 ws+ in mod (3*x+y) 10++sum2 :: (Num a) => [a] -> (a,a)+sum2 = List.foldl' (\(a,b) x -> strictPair b (a+x)) (0,0)++strictPair :: a -> b -> (a,b)+strictPair a b = ((,) $! a) $! b
+ src/Math/Checksum/IBAN.hs view
@@ -0,0 +1,65 @@+{- |+International Bank Account Number++<https://en.wikipedia.org/wiki/International_Bank_Account_Number>+-}+module Math.Checksum.IBAN (checksum, valid) where++import qualified Math.Checksum.Utility as Util++import qualified Control.Monad.Exception.Synchronous as ME+import Control.Monad.Exception.Synchronous (Exceptional(Success), throw)+import Control.Applicative (Applicative, liftA2, pure, (<$>))++import Data.Ix (inRange, index)+import Data.Bool.HT (if')+++{- |+> checksum "DE" "210501700012345678" == Success 68+-}+checksum :: String -> String -> Exceptional String Int+checksum country bban =+ (98-) . remainder <$>+ mapM intFromAlphaNum bban ++++ mapM intFromAlpha country +++ pure [(100,0)]++{- |+> valid "DE68210501700012345678" == Nothing+> valid "DE68210501700012345679" == Just "check sum does not match"+-}+valid :: String -> Maybe String+valid (country0:country1:sum0:sum1:bban) = Util.processValid $ do+ k <-+ remainder <$>+ mapM intFromAlphaNum bban ++++ mapM intFromAlpha [country0,country1] ++++ mapM intFromDigit [sum0,sum1]+ return (k==1)+valid _ = Just "too few characters"++infixr 5 +++++(+++) :: (Applicative f) => f [a] -> f [a] -> f [a]+(+++) = liftA2 (++)++remainder :: [(Int,Int)] -> Int+remainder = divide 97++divide :: Int -> [(Int,Int)] -> Int+divide divisor = foldl (\r (base,x) -> mod (base*r+x) divisor) 0++intFromDigit :: Char -> Exceptional String (Int,Int)+intFromDigit c = (,) 10 <$> Util.intFromDigit c++intFromAlpha :: Char -> Exceptional String (Int,Int)+intFromAlpha c =+ fmap ((,) 100 . (10+)) $+ if' (inRange ('a','z') c) (Success $ index ('a','z') c) $+ if' (inRange ('A','Z') c) (Success $ index ('A','Z') c) $+ throw $ "not a letter: " ++ [c]++intFromAlphaNum :: Char -> Exceptional String (Int,Int)+intFromAlphaNum c =+ ME.mapException (const $ "invalid alphanumeric character: " ++ [c]) $+ ME.alternative (intFromDigit c) (intFromAlpha c)
+ src/Math/Checksum/ISBN.hs view
@@ -0,0 +1,46 @@+{- |+International Standard Book Number++<https://en.wikipedia.org/wiki/International_Standard_Book_Number>++Covers only ISBN-10. For ISBN-13 simply use "Math.Checksum.EAN".+-}+module Math.Checksum.ISBN (checksum, valid) where++import qualified Math.Checksum.Utility as Util++import qualified Control.Monad.Exception.Synchronous as ME+import Control.Monad.Exception.Synchronous (Exceptional)+import Control.Monad (zipWithM)+import Control.Applicative ((<$>))+++{- |+> checksum "346811124" == Success 10+-}+checksum :: String -> Exceptional String Int+checksum xs = remainder [1..] <$> mapM Util.intFromDigit xs++{- |+> valid "346811124X" == Nothing+> valid "3468111240" == Just "check sum does not match"+-}+valid :: String -> Maybe String+valid xs = Util.processValid $ do+ ds <-+ zipWithM id+ (replicate 9 (Util.intFromDigit=<<) ++ (intFromCheckdigit=<<) :+ [ME.switch+ (const $ return 0) (const $ ME.throw "more than 10 characters")])+ (map return xs ++ [ME.throw "less than 10 characters"])+ return $ 0 == remainder weights ds++intFromCheckdigit :: Char -> Exceptional String Int+intFromCheckdigit 'X' = return 10+intFromCheckdigit c = Util.intFromDigit c++remainder :: [Int] -> [Int] -> Int+remainder ws ds = mod (sum (zipWith (*) ws ds)) 11++weights :: [Int]+weights = [1..10] ++ [-1]
+ src/Math/Checksum/Utility.hs view
@@ -0,0 +1,25 @@+module Math.Checksum.Utility where++import qualified Control.Monad.Exception.Synchronous as ME+import Control.Monad.Exception.Synchronous (Exceptional(Success), throw)++import Data.Ix (inRange, index)++import Data.List (unfoldr)+import Data.Maybe.HT (toMaybe)+import Data.Tuple.HT (swap)+++decomposePositional :: Integral a => a -> a -> [a]+decomposePositional b =+ unfoldr (\n -> toMaybe (n/=0) $ swap (divMod n b))++intFromDigit :: Char -> Exceptional String Int+intFromDigit c =+ if inRange ('0','9') c+ then Success $ index ('0','9') c+ else throw $ "not a digit: " ++ [c]++processValid :: Exceptional String Bool -> Maybe String+processValid =+ ME.switch Just (\b -> toMaybe (not b) "check sum does not match")