Skip to content

Instantly share code, notes, and snippets.

@jproyo
Created December 1, 2021 14:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jproyo/c2c134b79bd00ea566f1d6095ecc81ca to your computer and use it in GitHub Desktop.
Save jproyo/c2c134b79bd00ea566f1d6095ecc81ca to your computer and use it in GitHub Desktop.
First NoRedInk Tech Interview
module Format.Stdout where
import Control.Program
import qualified Data.Map.Strict as M
import Relude
output :: Histogram -> IO ()
output =
mapM_ (\(k, v) -> putStrLn $ show k ++ " " ++ replicate (fromIntegral v) '#') . M.assocs
module Data.Legislator where
import Control.Lens
import Data.Aeson
import Data.Char
import Data.Time.Calendar
import Data.Time.Clock
import Deriving.Aeson
import Relude
data ToLower
instance StringModifier ToLower where
getStringModifier "" = ""
getStringModifier (c : xs) = toLower c : xs
data Gender = M | F
deriving (Generic, Show, Eq, Ord)
deriving (FromJSON, ToJSON)
via CustomJSON '[OmitNothingFields] Gender
newtype Bio = Bio { _bGender :: Gender }
deriving (Generic, Show)
deriving (FromJSON, ToJSON)
via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "_b" ,CamelToSnake]] Bio
data TermType = Sen | Rep
deriving (Generic, Show, Eq, Ord)
deriving (ToJSON)
via CustomJSON '[OmitNothingFields, FieldLabelModifier '[ToLower]] TermType
instance FromJSON TermType where
parseJSON = parseJSON @Text >>= pure . \case
"sen" -> Sen
"rep" -> Rep
data Term = Term
{ _tType :: TermType
, _tStart :: UTCTime
, _tEnd :: UTCTime
}
deriving (Generic, Show)
deriving (FromJSON, ToJSON) via CustomJSON
'[OmitNothingFields , FieldLabelModifier '[StripPrefix "_t" , CamelToSnake]]
Term
data Legislator = Legislator
{ _lBio :: Bio
, _lTerms :: [Term]
}
deriving (Generic, Show)
deriving (FromJSON, ToJSON) via CustomJSON
'[OmitNothingFields , FieldLabelModifier '[StripPrefix "_l" , CamelToSnake]]
Legislator
makeLenses ''Legislator
makeLenses ''Term
isFemale :: Legislator -> Bool
isFemale = (== F) . _bGender . _lBio
isRep :: Term -> Bool
isRep = (== Rep) . _tType
toYear :: UTCTime -> Integer
toYear = fstf . toGregorian . utctDay where fstf (y, _, _) = y
years :: Term -> [Integer]
years t = [(t ^. tStart . to toYear) .. (t ^. tEnd . to toYear)]
module Legislator.LegislatorSpec
( spec
) where
import qualified Data.Map.Strict as M
import Data.Aeson
import LegislatorHistory
import Relude
import Test.Hspec
spec :: Spec
spec = do
describe "Run Test over some mocked Legislator List" $ do
it "Expected 4 years in ascending order" $ do
let
json''
= "[ { \"id\": { \"bioguide\": \"B000226\", \"govtrack\": 401222, \"icpsr\": 507, \"wikipedia\": \"Richard Bassett (Delaware politician)\", \"wikidata\": \"Q518823\", \"google_entity_id\": \"kg:/m/02pz46\" }, \"name\": { \"first\": \"Richard\", \"last\": \"Bassett\" }, \"bio\": { \"birthday\": \"1745-04-02\", \"gender\": \"M\" }, \"terms\": [ { \"type\": \"sen\", \"start\": \"1789-03-04\", \"end\": \"1793-03-03\", \"state\": \"DE\", \"class\": 2, \"party\": \"Anti-Administration\" } ] }, { \"id\": { \"bioguide\": \"B000546\", \"govtrack\": 401521, \"icpsr\": 786, \"house_history\": 9479, \"wikipedia\": \"Theodorick Bland (congressman)\", \"wikidata\": \"Q1749152\", \"google_entity_id\": \"kg:/m/033mf4\" }, \"name\": { \"first\": \"Theodorick\", \"last\": \"Bland\" }, \"bio\": { \"birthday\": \"1742-03-21\", \"gender\": \"M\" }, \"terms\": [ { \"type\": \"rep\", \"start\": \"1789-03-04\", \"end\": \"1791-03-03\", \"state\": \"VA\", \"district\": 9 } ] }, { \"id\": { \"bioguide\": \"B001086\", \"govtrack\": 402032, \"icpsr\": 1260, \"wikipedia\": \"Aedanus Burke\", \"house_history\": 10177, \"wikidata\": \"Q380504\", \"google_entity_id\": \"kg:/m/03yccv\" }, \"name\": { \"first\": \"Aedanus\", \"last\": \"Burke\" }, \"bio\": { \"birthday\": \"1743-06-16\", \"gender\": \"M\" }, \"terms\": [ { \"type\": \"rep\", \"start\": \"1789-03-04\", \"end\": \"1791-03-03\", \"state\": \"SC\", \"district\": 2 } ] }]"
let ls = eitherDecode @[Legislator] json''
case ls of
Left e -> expectationFailure $ show e
Right legislators -> M.null (histogram legislators) `shouldBe` False
module Control.Program where
import Control.Lens
import Data.Legislator
import qualified Data.Map.Strict as M
import Relude
-- TODO: Refine Types
type Year = Integer
type Histogram = M.Map Year Integer
histogram :: [Legislator] -> Histogram
histogram = foldl' buildHisto M.empty . filter isFemale
buildHisto :: Histogram -> Legislator -> Histogram
buildHisto h l =
let years' = foldMap years . filter isRep $ (l ^. lTerms)
in foldl' (flip (M.alter (maybe (Just 1) (Just . (+) 1)))) h years'
@jproyo
Copy link
Author

jproyo commented Dec 1, 2021

This part of the code in Legislators.hs file

instance FromJSON TermType where
  parseJSON = parseJSON @Text >>= pure . \case 
    "sen" -> Sen
    "rep" -> Rep

should be

instance FromJSON TermType where
  parseJSON (String o) = case o of
    "sen" -> pure Sen
    "rep" -> pure Rep
    e     -> fail $ "No String that matches representative type " <> toString e
  parseJSON _ = fail "No String that matches representative type"

And UTCTime type should be changed by Day because i didn't realize that the format is not in UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment