Created
November 24, 2018 06:32
-
-
Save lancelet/0ef0aae251f7831b23da7030ff2dbed3 to your computer and use it in GitHub Desktop.
FP vs OO; A Better Way to Count Candy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env stack | |
| {- stack | |
| script | |
| --resolver lts-12.19 | |
| --package containers | |
| --package double-conversion | |
| --package text | |
| --package text-show | |
| -} | |
| {- | |
| Haskell code from the blog post: "FP vs OO; A Better Way to Count Candy" | |
| by Jonathan Merritt. | |
| Install the Haskell Stack tool: https://docs.haskellstack.org/en/stable/README/ | |
| Run it as follows: | |
| $ ./candy.hs | |
| $ cat full_report.haskell.txt | |
| JellyBeans: 44 | |
| JellyBabies: 18 | |
| Beans per Baby: 2.44 | |
| $ cat summary.haskell.txt | |
| Beans per Baby: 2.44 | |
| -} | |
| {-# LANGUAGE OverloadedStrings #-} | |
| {-# LANGUAGE ScopedTypeVariables #-} | |
| import Data.Double.Conversion.Text (toFixed) | |
| import Data.Map.Strict (Map) | |
| import qualified Data.Map.Strict as Map | |
| import Data.Text (Text) | |
| import qualified Data.Text as T | |
| import Data.Text.IO (writeFile) | |
| import System.IO (FilePath) | |
| import TextShow (showt) | |
| import Prelude hiding (writeFile) | |
| newtype CandyBag = CandyBag (Map Text Int) | |
| candyBags :: [CandyBag] | |
| candyBags = | |
| [ CandyBag (Map.fromList | |
| [ ("jellybeans", 42) | |
| , ("jellybabies", 10) | |
| , ("frogs", 8) ]) | |
| , CandyBag (Map.fromList | |
| [ ("jellybeans", 2) | |
| , ("jellybabies", 8) | |
| , ("nerds", 11) ]) | |
| ] | |
| data JellyCount | |
| = JellyCount | |
| { beans :: Int | |
| , babies :: Int | |
| } | |
| instance Semigroup JellyCount where | |
| -- how to we smash two together? | |
| JellyCount a b <> JellyCount c d = JellyCount (a + c) (b + d) | |
| instance Monoid JellyCount where | |
| -- how do we get an empty JellyCount? | |
| mempty = JellyCount 0 0 | |
| countJelly :: CandyBag -> JellyCount | |
| countJelly (CandyBag m) = JellyCount beans babies | |
| where | |
| beans = Map.findWithDefault 0 "jellybeans" m | |
| babies = Map.findWithDefault 0 "jellybabies" m | |
| data SummarizedJellyCount | |
| = SummarizedJellyCount | |
| { basicCount :: JellyCount -- our basic information | |
| , beansPerBaby :: Double -- extra information (ooo... expensive!) | |
| } | |
| summarize :: JellyCount -> SummarizedJellyCount | |
| summarize j = SummarizedJellyCount j bpb | |
| where | |
| bpb = fromIntegral (beans j) / fromIntegral (babies j) | |
| serializeSummary :: SummarizedJellyCount -> Text | |
| serializeSummary (SummarizedJellyCount _ bpb) | |
| = "Beans per Baby: " <> toFixed 2 bpb <> "\n" | |
| serializeFullReport :: SummarizedJellyCount -> Text | |
| serializeFullReport s@(SummarizedJellyCount (JellyCount beans babies) _) | |
| = "JellyBeans: " <> showt beans <> "\n" | |
| <> "JellyBabies: " <> showt babies <> "\n" | |
| <> serializeSummary s | |
| writeSummary :: FilePath -> SummarizedJellyCount -> IO () | |
| writeSummary fp s = writeFile fp (serializeSummary s) | |
| writeFullReport :: FilePath -> SummarizedJellyCount -> IO () | |
| writeFullReport fp s = writeFile fp (serializeFullReport s) | |
| main :: IO () | |
| main = do | |
| let | |
| -- We make a list of JellyCount, one for each bag of candy. | |
| -- fmap is a standard function which applies our countJelly | |
| -- function to each CandyBag in the list, producing a list as | |
| -- output. This is (roughly) equivalent to part of the Python | |
| -- for loop above. | |
| counts :: [JellyCount] = fmap countJelly candyBags | |
| -- We smash together all the counts to get the final count. | |
| -- mconcat is another standard function that lets us smash | |
| -- together a whole list that contains monoid elements. | |
| finalCount :: JellyCount = mconcat counts | |
| -- Do the summary | |
| summarizedCount = summarize finalCount | |
| -- The IO actions which create the reports: | |
| writeSummary "summary.haskell.txt" summarizedCount | |
| writeFullReport "full_report.haskell.txt" summarizedCount |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| # | |
| # Python code from the blog post: "FP vs OO; A Better Way to Count Candy" | |
| # by Jonathan Merritt. | |
| # | |
| # Run it as follows: | |
| # | |
| # $ python3 candy.py | |
| # | |
| # $ cat full_report.python.txt | |
| # JellyBeans: 44 | |
| # JellyBabies: 18 | |
| # Beans per Baby: 2.44 | |
| # | |
| # $ cat summary.python.txt | |
| # Beans per Baby: 2.44 | |
| candy_bags = [ | |
| { 'jellybeans': 42, | |
| 'jellybabies': 10, | |
| 'frogs': 8 }, | |
| { 'jellybeans': 2, | |
| 'jellybabies': 8, | |
| 'nerds': 11 } ] | |
| class JellyCounter: | |
| def __init__(self, report_path: str): | |
| self.beans_count = 0 | |
| self.babies_count = 0 | |
| self.beans_per_baby = 0.0 | |
| self.report_path = report_path | |
| def add_bag(self, candy_bag): | |
| self.beans_count += candy_bag['jellybeans'] | |
| self.babies_count += candy_bag['jellybabies'] | |
| def calculate_fraction(self): | |
| self.beans_per_baby = self.beans_count / self.babies_count | |
| def write_summary(self): | |
| self.calculate_fraction() | |
| with open(f'{self.report_path}/summary.python.txt', 'w') as out_file: | |
| out_file.write(f'Beans per Baby: {self.beans_per_baby:.2f}\n') | |
| def write_full_report(self): | |
| with open(f'{self.report_path}/full_report.python.txt', 'w') as out_file: | |
| out_file.write(f'JellyBeans: {self.beans_count}\n') | |
| out_file.write(f'JellyBabies: {self.babies_count}\n') | |
| out_file.write(f'Beans per Baby: {self.beans_per_baby:.2f}\n') | |
| if __name__ == '__main__': | |
| counter = JellyCounter(report_path='.') | |
| for bag in candy_bags: | |
| counter.add_bag(bag) | |
| counter.write_summary() | |
| counter.write_full_report() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment