Skip to content

Commit 6745f0a

Browse files
committed
ref: lib: Use sparse accounts to increase efficiency and flexibility.
1 parent 79ff3fc commit 6745f0a

File tree

5 files changed

+36
-32
lines changed

5 files changed

+36
-32
lines changed

Diff for: hledger-lib/Hledger/Data/Account.hs

+13-21
Original file line numberDiff line numberDiff line change
@@ -107,20 +107,20 @@ accountFromBalances name bal = Account
107107
-- The accounts are returned as a list in flattened tree order,
108108
-- and also reference each other as a tree.
109109
-- (The first account is the root of the tree.)
110-
accountsFromPostings :: (Posting -> Day) -> [Day] -> [Posting] -> [Account AccountBalance]
111-
accountsFromPostings getPostingDate days = flattenAccounts . accountFromPostings getPostingDate days
110+
accountsFromPostings :: (Posting -> Maybe Day) -> [Posting] -> [Account AccountBalance]
111+
accountsFromPostings getPostingDate = flattenAccounts . accountFromPostings getPostingDate
112112

113113
-- | Derive 1. an account tree and 2. each account's total exclusive
114114
-- and inclusive changes from a list of postings.
115115
-- This is the core of the balance command (and of *ledger).
116116
-- The accounts are returned as tree.
117-
accountFromPostings :: (Posting -> Day) -> [Day] -> [Posting] -> Account AccountBalance
118-
accountFromPostings getPostingDate days ps =
117+
accountFromPostings :: (Posting -> Maybe Day) -> [Posting] -> Account AccountBalance
118+
accountFromPostings getPostingDate ps =
119119
tieAccountParents . sumAccounts $ mapAccounts setBalance acctTree
120120
where
121121
-- The special name "..." is stored in the root of the tree
122122
acctTree = accountTree "root" . HM.keys $ HM.delete "..." accountMap
123-
setBalance a = a{abalances = HM.lookupDefault emptyMap name accountMap}
123+
setBalance a = a{abalances = HM.lookupDefault mempty name accountMap}
124124
where name = if aname a == "root" then "..." else aname a
125125
accountMap = processPostings ps
126126

@@ -130,9 +130,7 @@ accountFromPostings getPostingDate days ps =
130130
processAccountName p = HM.alter (updateAccountBalance p) (paccount p)
131131
updateAccountBalance p = Just
132132
. insertAccountBalances (getPostingDate p) (AccountBalance 1 (pamount p) nullmixedamt)
133-
. fromMaybe emptyMap
134-
135-
emptyMap = emptyAccountBalances days
133+
. fromMaybe mempty
136134

137135
-- | Convert a list of account names to a tree of Account objects,
138136
-- with just the account names filled in.
@@ -208,12 +206,12 @@ sumAccounts a = a{asubs = subs, abalances = setImplicitBalances $ abalances a}
208206
subtotals = foldMap abalances subs
209207

210208
setImplicitBalances :: AccountBalances AccountBalance -> AccountBalances AccountBalance
211-
setImplicitBalances = if null subs
212-
then fmap setibal
213-
else opAccountBalances addibal subtotals
209+
setImplicitBalances = mergeAccountBalances combineChildren (fmap onlyChildren) (fmap noChildren) subtotals
210+
211+
combineChildren children this = this {abibalance = abebalance this <> abibalance children}
212+
onlyChildren children = mempty{abibalance = abibalance children}
213+
noChildren this = this {abibalance = abebalance this}
214214

215-
setibal bal@(AccountBalance _ ebal _) = bal{abibalance = ebal}
216-
addibal (AccountBalance _ _ ibal) bal@(AccountBalance _ ebal _) = bal{abibalance = ebal <> ibal}
217215

218216
-- | Remove all subaccounts below a certain depth.
219217
clipAccounts :: Int -> Account a -> Account a
@@ -288,12 +286,6 @@ filterAccounts p a
288286
-- | Merge two accounts and their subaccounts.
289287
--
290288
-- This assumes that the top-level 'Account's have the same name.
291-
--
292-
-- Unless the 'Account's and all their subaccounts have the same collection of
293-
-- 'Day' keys, it will probably produce unhelpful output: do not do this unless
294-
-- you really know what you're doing. Merging two accounts with unequal
295-
-- 'Day' keys can be useful when they have the same Intervals but not
296-
-- necessarily equal spans, as in the budget reports.
297289
mergeAccounts :: Account a -> Account b -> Account (These a b)
298290
mergeAccounts a = tieAccountParents . merge a
299291
where
@@ -384,9 +376,9 @@ showAccountDebug a = printf "%-25s %s %4s"
384376
tests_Account = testGroup "Account" [
385377
testGroup "accountFromPostings" [
386378
testCase "no postings, no days" $
387-
accountFromPostings undefined [] [] @?= accountTree "root" []
379+
accountFromPostings undefined [] @?= accountTree "root" []
388380
,testCase "no postings, only 2000-01-01" $
389381
allAccounts (all (\d -> (ModifiedJulianDay $ toInteger d) == fromGregorian 2000 01 01) . IM.keys . abdatemap . abalances)
390-
(accountFromPostings undefined [fromGregorian 2000 01 01] []) @? "Not all abalances have exactly 2000-01-01"
382+
(accountFromPostings undefined []) @? "Not all abalances have exactly 2000-01-01"
391383
]
392384
]

Diff for: hledger-lib/Hledger/Data/AccountBalance.hs

+10-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Hledger.Data.AccountBalance
1414
, insertAccountBalances
1515
, opAccountBalances
1616
, mergeAccountBalances
17+
, padAccountBalances
1718

1819
, applyAccountBalance
1920
, opAccountBalance
@@ -25,6 +26,7 @@ module Hledger.Data.AccountBalance
2526

2627
import Data.Foldable1 (Foldable1(..))
2728
import qualified Data.IntMap.Strict as IM
29+
import qualified Data.IntSet as IS
2830
#if !MIN_VERSION_base(4,20,0)
2931
import Data.List (foldl')
3032
#endif
@@ -82,10 +84,10 @@ lookupAccountBalance d (AccountBalances h as) =
8284
maybe h snd $ IM.lookupLE (fromInteger $ toModifiedJulianDay d) as
8385

8486
-- | Add the 'AccountBalance' to the appropriate location in 'AccountBalances'.
85-
insertAccountBalances :: Semigroup a => Day -> a -> AccountBalances a -> AccountBalances a
86-
insertAccountBalances day b balances = case IM.lookupLE (fromInteger $ toModifiedJulianDay day) (abdatemap balances) of
87-
Nothing -> balances{abhistorical = b <> abhistorical balances}
88-
Just (d, a) -> balances{abdatemap = IM.insert d (b <> a) $ abdatemap balances}
87+
insertAccountBalances :: Semigroup a => Maybe Day -> a -> AccountBalances a -> AccountBalances a
88+
insertAccountBalances mday b balances = case mday of
89+
Nothing -> balances{abhistorical = abhistorical balances <> b}
90+
Just day -> balances{abdatemap = IM.insertWith (<>) (fromInteger $ toModifiedJulianDay day) b $ abdatemap balances}
8991

9092
-- | Performs an operation on the contents of two 'AccountBalances'.
9193
--
@@ -102,6 +104,10 @@ mergeAccountBalances f only1 only2 = \(AccountBalances h1 as1) (AccountBalances
102104
where
103105
merge = IM.mergeWithKey (\_ x1 x2 -> Just $ f x1 x2) only1 only2
104106

107+
-- | Pad out the datemap of an 'AccountBalances' so that every key from a set is present.
108+
padAccountBalances :: Monoid a => IS.IntSet -> AccountBalances a -> AccountBalances a
109+
padAccountBalances keys bal = bal{abdatemap = abdatemap bal <> IM.fromSet (const mempty) keys}
110+
105111

106112
instance Show AccountBalance where
107113
showsPrec d (AccountBalance n e i) =

Diff for: hledger-lib/Hledger/Data/Ledger.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ ledgerFromJournal q j = nullledger{ljournal=j'', laccounts=as}
6363
j' = filterJournalAmounts (filterQuery queryIsSym q) $ -- remove amount parts which the query's sym: terms would exclude
6464
filterJournalPostings q' j
6565
-- Ledger does not use date-separated balances, so dates are left empty
66-
as = accountsFromPostings (const nulldate) [] $ journalPostings j'
66+
as = accountsFromPostings (const $ Just nulldate) $ journalPostings j'
6767
j'' = filterJournalPostings depthq j'
6868

6969
-- | List a ledger's account names.

Diff for: hledger-lib/Hledger/Reports/MultiBalanceReport.hs

+11-5
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ import Data.List (sortOn)
3939
import Data.List.NonEmpty (NonEmpty((:|)))
4040
import qualified Data.HashSet as HS
4141
import qualified Data.IntMap.Strict as IM
42+
import qualified Data.IntSet as IS
4243
import Data.Maybe (fromMaybe, isJust, mapMaybe)
4344
import Data.Ord (Down(..))
4445
import Data.Semigroup (sconcat)
4546
import Data.These (these)
46-
import Data.Time.Calendar (Day, addDays, fromGregorian)
47+
import Data.Time.Calendar (Day(..), addDays, fromGregorian)
4748
import Data.Traversable (mapAccumL)
4849

4950
import Hledger.Data
@@ -291,13 +292,18 @@ calculateReportAccount rspec@ReportSpec{_rsReportOpts=ropts} j priceoracle colsp
291292
cumulative = cumulativeSum changes{abhistorical = mempty}
292293
avalue = accountBalancesValuation ropts j priceoracle colspans
293294

294-
changesAcct = dbg5With (\x -> "multiBalanceReport changesAcct\n" ++ showAccounts x) $
295-
accountFromPostings getDate intervalStarts ps
295+
changesAcct = dbg5With (\x -> "multiBalanceReport changesAcct\n" ++ showAccounts x) .
296+
mapAccounts (\a -> a{abalances = padAccountBalances intervalStarts $ abalances a}) $
297+
accountFromPostings getIntervalStartDate ps
296298

297-
getDate = postingDateOrDate2 (whichDate (_rsReportOpts rspec))
298-
intervalStarts = case mapMaybe spanStart colspans of
299+
getIntervalStartDate p = intToDay <$> IS.lookupLE (dayToInt $ getPostingDate p) intervalStarts
300+
getPostingDate = postingDateOrDate2 (whichDate (_rsReportOpts rspec))
301+
302+
intervalStarts = IS.fromList . map dayToInt $ case mapMaybe spanStart colspans of
299303
[] -> [nulldate] -- Deal with the case of the empty journal
300304
xs -> xs
305+
dayToInt = fromInteger . toModifiedJulianDay
306+
intToDay = ModifiedJulianDay . toInteger
301307

302308
-- | The valuation function to use for the chosen report options.
303309
accountBalancesValuation :: ReportOpts -> Journal -> PriceOracle -> [DateSpan]

Diff for: hledger-lib/Hledger/Reports/PostingsReport.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ summarisePostingsInDateSpan spn@(DateSpan b e) wd mdepth showempty ps
243243
summarypes = map (, dateSpanAsPeriod spn) $ (if showempty then id else filter (not . mixedAmountLooksZero . pamount)) summaryps
244244
anames = nubSort $ map paccount ps
245245
-- aggregate balances by account, like ledgerFromJournal, then do depth-clipping
246-
accts = accountsFromPostings postingDate [] ps
246+
accts = accountsFromPostings (const Nothing) ps
247247
balance a = maybe nullmixedamt bal $ lookupAccount a accts
248248
where
249249
bal = (if isclipped a then abibalance else abebalance) . abhistorical . abalances

0 commit comments

Comments
 (0)