packages feed

kanji 3.2.1 → 3.3.0

raw patch · 7 files changed

+91/−76 lines, 7 filesdep +transformersdep −microlens-platformdep −protoludePVP ok

version bump matches the API change (PVP)

Dependencies added: transformers

Dependencies removed: microlens-platform, protolude

API changes (from Hackage documentation)

- Data.Kanji: averageLevel :: [Kanji] -> Maybe Float
- Data.Kanji: kanjiDensity :: Int -> [Kanji] -> Float
- Data.Kanji.Types: numericLevel :: Level -> Float
- Data.Kanji.Types: rankMap :: Map Level Float
+ Data.Kanji: Hanzi :: CharCat
+ Data.Kanji: Hiragana :: CharCat
+ Data.Kanji: Katakana :: CharCat
+ Data.Kanji: Numeral :: CharCat
+ Data.Kanji: Other :: CharCat
+ Data.Kanji: Punctuation :: CharCat
+ Data.Kanji: RomanLetter :: CharCat
+ Data.Kanji: Unknown :: Level
+ Data.Kanji: category :: Char -> CharCat
+ Data.Kanji: data CharCat
+ Data.Kanji: densities :: Text -> Map CharCat Float
+ Data.Kanji: isHiragana :: Char -> Bool
+ Data.Kanji: isKatakana :: Char -> Bool
+ Data.Kanji.Types: Hanzi :: CharCat
+ Data.Kanji.Types: Hiragana :: CharCat
+ Data.Kanji.Types: Katakana :: CharCat
+ Data.Kanji.Types: Numeral :: CharCat
+ Data.Kanji.Types: Other :: CharCat
+ Data.Kanji.Types: Punctuation :: CharCat
+ Data.Kanji.Types: RomanLetter :: CharCat
+ Data.Kanji.Types: Unknown :: Level
+ Data.Kanji.Types: category :: Char -> CharCat
+ Data.Kanji.Types: data CharCat
+ Data.Kanji.Types: instance Control.DeepSeq.NFData Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance Data.Aeson.Types.FromJSON.FromJSON Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance Data.Aeson.Types.ToJSON.ToJSON Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance Data.Aeson.Types.ToJSON.ToJSONKey Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance Data.Hashable.Class.Hashable Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance GHC.Classes.Eq Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance GHC.Classes.Ord Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance GHC.Generics.Generic Data.Kanji.Types.CharCat
+ Data.Kanji.Types: instance GHC.Show.Show Data.Kanji.Types.CharCat
+ Data.Kanji.Types: isHiragana :: Char -> Bool
+ Data.Kanji.Types: isKatakana :: Char -> Bool
- Data.Kanji: level :: Kanji -> Maybe Level
+ Data.Kanji: level :: Kanji -> Level

Files

CHANGELOG.md view
@@ -1,3 +1,11 @@+## 3.3.0++- New `Unknown` level for Kanji above level `Two`+- `level` is now a total function+- Removed `averageLevel`, as it wasn't correct nor useful+- New `densities` function with associated type `CharCat` for getting more+  interesting character density information+ ## 3.2.1  - `ToJSON` and `FromJSON` instances for `Kanji` derived via `Generic`
README.md view
@@ -28,16 +28,14 @@ command-line options are available:  ```-Usage: kanji [-u|--unknowns] [-d|--density] [-e|--elementary] [-l|--leveldist]-             [-a|--average] [-s|--splits] ((-f|--file ARG) | JAPANESE)+Usage: kanji [-d|--density] [-e|--elementary] [-l|--leveldist] [-s|--splits]+             ((-f|--file ARG) | JAPANESE)  Available options:   -h,--help                Show this help text-  -u,--unknowns            Find Kanji whose Level couldn't be determined   -d,--density             Find how much of the input is made of Kanji   -e,--elementary          Find density of Kanji learnt in elementary school   -l,--leveldist           Find the distribution of Kanji levels-  -a,--average             Find the average Level of all Kanji present   -s,--splits              Show which Level each Kanji belongs to   -f,--file ARG            Take input from a file ```@@ -74,19 +72,21 @@  *All options* ```-$> kanji -leadus これは日本語+$> kanji -sled これは日本語。串と糞 {     "levelSplit": {         "Nine": ["語"],-        "Ten": ["本", "日"]+        "Ten": ["本", "日"],+        "Unknown": ["糞"],+        "Two": ["串"]     },-    "elementary": 1,-    "average": 9.666667,+    "elementary": 0.6,     "density": 0.5,-    "unknowns": [],     "distributions": {-        "Nine": 0.33333334,-        "Ten": 0.6666667+        "Nine": 0.2,+        "Ten": 0.4,+        "Unknown": 0.2,+        "Two": 0.2     } } ```
kanji.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 151854e92d22569c786a39e97c1a9922bfe2a86908bb691351fe7c16d8095572+-- hash: 540350499b57a0eb12fb2b4340b009e5daf99fef4eba303fd0e645a289e75063  name:           kanji-version:        3.2.1+version:        3.3.0 synopsis:       Perform 漢字検定 (Japan Kanji Aptitude Test) level analysis on Japanese Kanji description:    Perform 漢字検定 (Japan Kanji Aptitude Test) level analysis on Japanese Kanji. category:       Data@@ -43,7 +43,7 @@   main-is: nanq.hs   hs-source-dirs:       nanq-  default-extensions: NoImplicitPrelude OverloadedStrings+  default-extensions: OverloadedStrings   ghc-options: -fwarn-unused-imports -fwarn-unused-binds -fwarn-name-shadowing -fwarn-unused-matches -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns -O2   build-depends:       aeson >=1.2 && <1.4@@ -53,10 +53,9 @@     , kanji     , microlens >=0.4 && <0.5     , microlens-aeson >=2.2 && <2.4-    , microlens-platform     , optparse-applicative >=0.14 && <0.15-    , protolude >=0.2 && <0.3     , text+    , transformers   default-language: Haskell2010  test-suite kanji-test
lib/Data/Kanji.hs view
@@ -16,17 +16,19 @@          Kanji        , kanji, _kanji        , allKanji-       , isKanji+       , isKanji, isHiragana, isKatakana+         -- * Character Categories+       , CharCat(..)+       , category          -- * Levels        , Level(..)        , level          -- * Analysis        , percentSpread        , levelDist-       , averageLevel        , uniques          -- ** Densities-       , kanjiDensity+       , densities        , elementaryDen        , middleDen        , highDen@@ -34,14 +36,14 @@        ) where  import           Control.Arrow hiding (second)-import           Data.Foldable (foldl', fold)+import           Data.Foldable (fold) import           Data.Kanji.Levels import           Data.Kanji.Types import           Data.List (sort, group) import qualified Data.Map.Strict as M-import           Data.Maybe (catMaybes) import           Data.Semigroup ((<>)) import qualified Data.Set as S+import qualified Data.Text as T  --- @@ -55,15 +57,16 @@ allKanji' :: M.Map Kanji Level allKanji' = M.fromList . S.toList . fold $ M.mapWithKey (\k v -> S.map (,k) v) allKanji --- | What `Level` does a Kanji belong to?-level :: Kanji -> Maybe Level-level k = M.lookup k allKanji'+-- | What `Level` does a Kanji belong to? `Unknown` for Kanji above level `Two`.+level :: Kanji -> Level+level k = maybe Unknown id $ M.lookup k allKanji' {-# INLINE level #-} --- | Given the length of some String-like type and a list of `Kanji` found therein,--- what percentage of them were Kanji?-kanjiDensity :: Int -> [Kanji] -> Float-kanjiDensity len ks = fromIntegral (length ks) / fromIntegral len+-- | Percentage of appearance of each `CharCat` in the source text.+-- The percentages will sum to 1.0.+densities :: T.Text -> M.Map CharCat Float+densities t = M.fromList . map (head &&& f) . group . sort . map category $ T.unpack t+  where f xs = fromIntegral (length xs) / fromIntegral (T.length t)  -- | How much of the Kanji found are learnt in elementary school in Japan? --@@ -89,16 +92,10 @@ adultDen :: M.Map Level Float -> Float adultDen m = M.foldl' (+) 0 . M.restrictKeys m $ S.fromList [ Two, PreTwo .. ] --- | Find the average `Level` of a given set of `Kanji`.-averageLevel :: [Kanji] -> Maybe Float-averageLevel [] = Nothing-averageLevel ks = Just . average . map numericLevel . catMaybes $ map level ks-  where average ns = foldl' (+) 0 ns / fromIntegral (length ns)- -- | How much of each `Level` is represented by a group of Kanji?--- The distribution values will sum to <= 1.+-- The distribution values will sum to 1. levelDist :: [Kanji] -> M.Map Level Float-levelDist ks = M.fromList . map percentPair . group . sort . catMaybes $ map level ks+levelDist ks = M.fromList . map percentPair . group . sort $ map level ks   where percentPair qns = (head qns, fromIntegral (length qns) / totalKs)         totalKs = fromIntegral $ length ks @@ -117,4 +114,4 @@ -- | Which Kanji appeared from each Level in the text? uniques :: [Kanji] -> M.Map Level (S.Set Kanji) uniques = S.foldl' h M.empty . S.fromList-  where h a k = maybe a (\l -> M.insertWith (<>) l (S.singleton k) a) $ level k+  where h a k = (\l -> M.insertWith (<>) l (S.singleton k) a) $ level k
lib/Data/Kanji/Types.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveAnyClass, DeriveGeneric #-}+{-# LANGUAGE ViewPatterns #-}  -- | -- Module    : Data.Kanji.Types@@ -16,10 +17,8 @@ import           Data.Aeson import           Data.Aeson.Encoding (text) import           Data.Bool (bool)-import           Data.Char (ord)+import           Data.Char (ord, isLetter, isNumber, isPunctuation) import           Data.Hashable-import qualified Data.Map.Strict as M-import           Data.Maybe (fromJust) import qualified Data.Text as T import           GHC.Generics @@ -61,7 +60,7 @@ -- Level data for Kanji above Level-2 is currently not provided by -- this library. data Level = Ten | Nine | Eight | Seven | Six | Five | Four | Three | PreTwo-           | Two | PreOne | One+           | Two | PreOne | One | Unknown            deriving (Eq, Ord, Enum, Show, Generic, Hashable, NFData, ToJSON, FromJSON)  instance ToJSONKey Level where@@ -69,17 +68,39 @@     where f = T.pack . show           g = text . T.pack . show --- | Discover a `Level`'s numeric representation, as a `Float`.-numericLevel :: Level -> Float-numericLevel = fromJust . flip M.lookup rankMap+-- | Legal Kanji appear between UTF-8 characters 19968 and 40959.+isKanji :: Char -> Bool+isKanji (ord -> c) = 19968 <= c && c <= 40959+{-# INLINE isKanji #-} --- | A mapping of Ranks to their numeric representation.-rankMap :: M.Map Level Float-rankMap = M.fromList $ zip [Ten ..] [10,9,8,7,6,5,4,3,2.5,2,1.5,1]+-- | あ to ん.+isHiragana :: Char -> Bool+isHiragana (ord -> c) = 0x3040 <= c && c <= 0x309f+{-# INLINE isHiragana #-} --- | Legal Kanji appear between UTF8 characters 19968 and 40959.-isKanji :: Char -> Bool-isKanji c = lowLimit <= c' && c' <= highLimit-    where c' = ord c-          lowLimit  = 19968  -- This is `一`-          highLimit = 40959  -- I don't have the right fonts to display this.+-- | ア to ン.+isKatakana :: Char -> Bool+isKatakana (ord -> c) = 0x30a0 <= c && c <= 0x30ff+{-# INLINE isKatakana #-}++-- | General categories for characters, at least as is useful for thinking about+-- Japanese.+--+-- Japanese "full-width" numbers and letters will be counted as `Numeral`+-- and `RomanLetter` respectively, alongside their usual ASCII forms.+data CharCat = Hanzi | Hiragana | Katakana | Numeral | RomanLetter | Punctuation | Other+  deriving (Eq, Ord, Show, Generic, Hashable, NFData, ToJSON, FromJSON)++category :: Char -> CharCat+category c | isKanji c       = Hanzi+           | isHiragana c    = Hiragana+           | isKatakana c    = Katakana+           | isLetter c      = RomanLetter+           | isNumber c      = Numeral+           | isPunctuation c = Punctuation+           | otherwise       = Other++instance ToJSONKey CharCat where+  toJSONKey = ToJSONKeyText f g+    where f = T.pack . show+          g = text . T.pack . show
nanq/nanq.hs view
@@ -3,27 +3,28 @@  module Main ( main ) where +import           Control.Monad.Trans.Reader import           Data.Aeson import           Data.Aeson.Encode.Pretty import           Data.Kanji import qualified Data.Map.Strict as M-import           Data.Maybe (isNothing)+import           Data.Maybe (catMaybes, mapMaybe)+import           Data.Monoid ((<>)) import qualified Data.Set as S+import           Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as TIO import           Data.Text.Lazy.Builder (toLazyText)+import           Data.Text.Lazy.IO as TLIO import           Lens.Micro import           Lens.Micro.Aeson-import           Lens.Micro.Platform () import           Options.Applicative-import           Protolude hiding (to)  ---  data Flags = Flags [Operation] (Either FilePath Text) deriving (Eq) -data Operation = Unknowns | Density | Elementary-               | Distribution | Average | Splits deriving (Eq)+data Operation = Density | Elementary | Distribution | Splits deriving (Eq)  data Env = Env { _allKs :: [Kanji]                , _original :: Text } deriving Eq@@ -39,16 +40,12 @@ operations :: Parser [Operation] operations = catMaybes <$> ops   where ops = traverse optional-          [ flag' Unknowns $-            lsh "unknowns" 'u' "Find Kanji whose Level couldn't be determined"-          , flag' Density $+          [ flag' Density $             lsh "density" 'd' "Find how much of the input is made of Kanji"           , flag' Elementary $             lsh "elementary" 'e' "Find density of Kanji learnt in elementary school"           , flag' Distribution $             lsh "leveldist" 'l' "Find the distribution of Kanji levels"-          , flag' Average $-            lsh "average" 'a' "Find the average Level of all Kanji present"           , flag' Splits $             lsh "splits" 's' "Show which Level each Kanji belongs to" ] @@ -56,22 +53,16 @@ ob :: ToJSON v => Text -> v -> Value ob k v = object [ k .= v ] -averageLev :: Reader Env Value-averageLev = ob "average" . averageLevel <$> asks _allKs- splits :: Reader Env Value splits = ob "levelSplit" . S.foldl' f mempty . S.fromList <$> asks _allKs-  where f a k = maybe a (\l -> M.insertWith (++) l [k] a) $ level k--unknowns :: Reader Env Value-unknowns = ob "unknowns" . S.map _kanji . S.filter (isNothing . level) . S.fromList <$> asks _allKs+  where f a k = (\l -> M.insertWith (++) l [k] a) $ level k  distribution :: Reader Env Value distribution = ob "distributions" . levelDist <$> asks _allKs  density :: Reader Env Value density = do-  d <- kanjiDensity <$> asks (T.length . _original) <*> asks _allKs+  d <- maybe 0 id . M.lookup Hanzi . densities <$> asks _original   pure $ ob "density" d  elementaryDensity :: Reader Env Value@@ -79,15 +70,13 @@  -- | All operations return JSON, to be aggregated into a master Object. execOp :: Operation -> Reader Env Value-execOp Unknowns = unknowns execOp Density = density execOp Elementary = elementaryDensity execOp Distribution = distribution-execOp Average = averageLev execOp Splits = splits  output :: Value -> IO ()-output = putLText . toLazyText . encodePrettyToTextBuilder+output = TLIO.putStrLn . toLazyText . encodePrettyToTextBuilder  -- | Dispatch on each `Operation` given. Aggregates the resulting JSON. work :: (Env, [Operation]) -> Value@@ -98,7 +87,7 @@ env (Flags os inp) = case inp of   Right t -> pure (e t, os)   Left  f -> (, os) . e <$> TIO.readFile f-  where e t = Env (t ^.. each . to kanji . _Just) t+  where e t = Env (mapMaybe kanji $ T.unpack t) t  main :: IO () main = execParser opts >>= env >>= output . work
test/Test.hs view
@@ -22,6 +22,7 @@   [ testGroup "Analysis"     [       testCase "percentSpread totals 1.0" $ M.foldl' (+) 0 (percentSpread testKanji) @?~ 1.0+    , testCase "levelDist totals 1.0"     $ M.foldl' (+) 0 (levelDist testKanji) @?~ 1.0     , testCase "levelDist == adultDen"    $ M.foldl' (+) 0 (levelDist testKanji) @?~ adultDen (levelDist testKanji)     ]   , testGroup "JSON"@@ -38,7 +39,7 @@   where ?epsilon = 0.001  testKanji :: [Kanji]-testKanji = map Kanji "本猫机鉛筆紙帽子包丁床天井壁光電気棚箱靴家階段戸屋根地面糞"+testKanji = map Kanji "本猫机鉛筆紙帽子包丁床天井壁光電気棚箱靴家階段戸屋根地面"  isError :: Result a -> Bool isError (Error _) = True