lib: normaliseHelper now uses a strict Map for combining amounts
internally, closing a big space leak. This also now combines Amounts with TotalPrices in the same commodity when normalising; amounts with TotalPrices were previously never combined.
This commit is contained in:
parent
ecca7f4e0c
commit
9d527a9926
@ -40,6 +40,7 @@ exchange rates.
|
|||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE BangPatterns #-}
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
@ -144,12 +145,10 @@ module Hledger.Data.Amount (
|
|||||||
import Control.Monad (foldM)
|
import Control.Monad (foldM)
|
||||||
import Data.Decimal (DecimalRaw(..), decimalPlaces, normalizeDecimal, roundTo)
|
import Data.Decimal (DecimalRaw(..), decimalPlaces, normalizeDecimal, roundTo)
|
||||||
import Data.Default (Default(..))
|
import Data.Default (Default(..))
|
||||||
import Data.Function (on)
|
import Data.Foldable (toList)
|
||||||
import Data.List (groupBy, intercalate, intersperse, mapAccumL, partition,
|
import Data.List (intercalate, intersperse, mapAccumL, partition)
|
||||||
sortBy)
|
|
||||||
import Data.List.NonEmpty (NonEmpty(..), nonEmpty)
|
import Data.List.NonEmpty (NonEmpty(..), nonEmpty)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map.Strict as M
|
||||||
import Data.Map (findWithDefault)
|
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
#if !(MIN_VERSION_base(4,11,0))
|
#if !(MIN_VERSION_base(4,11,0))
|
||||||
import Data.Semigroup ((<>))
|
import Data.Semigroup ((<>))
|
||||||
@ -246,8 +245,8 @@ amt @@ priceamt = amt{aprice=Just $ TotalPrice priceamt}
|
|||||||
-- Prices are ignored and discarded.
|
-- Prices are ignored and discarded.
|
||||||
-- Remember: the caller is responsible for ensuring both amounts have the same commodity.
|
-- Remember: the caller is responsible for ensuring both amounts have the same commodity.
|
||||||
similarAmountsOp :: (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
|
similarAmountsOp :: (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
|
||||||
similarAmountsOp op Amount{acommodity=_, aquantity=q1, astyle=AmountStyle{asprecision=p1}}
|
similarAmountsOp op !Amount{acommodity=_, aquantity=q1, astyle=AmountStyle{asprecision=p1}}
|
||||||
Amount{acommodity=c2, aquantity=q2, astyle=s2@AmountStyle{asprecision=p2}} =
|
!Amount{acommodity=c2, aquantity=q2, astyle=s2@AmountStyle{asprecision=p2}} =
|
||||||
-- trace ("a1:"++showAmountDebug a1) $ trace ("a2:"++showAmountDebug a2) $ traceWith (("= :"++).showAmountDebug)
|
-- trace ("a1:"++showAmountDebug a1) $ trace ("a2:"++showAmountDebug a2) $ traceWith (("= :"++).showAmountDebug)
|
||||||
amount{acommodity=c2, aquantity=q1 `op` q2, astyle=s2{asprecision=max p1 p2}}
|
amount{acommodity=c2, aquantity=q1 `op` q2, astyle=s2{asprecision=max p1 p2}}
|
||||||
-- c1==c2 || q1==0 || q2==0 =
|
-- c1==c2 || q1==0 || q2==0 =
|
||||||
@ -559,24 +558,18 @@ normaliseMixedAmount = normaliseHelper False
|
|||||||
|
|
||||||
normaliseHelper :: Bool -> MixedAmount -> MixedAmount
|
normaliseHelper :: Bool -> MixedAmount -> MixedAmount
|
||||||
normaliseHelper squashprices (Mixed as)
|
normaliseHelper squashprices (Mixed as)
|
||||||
| missingamt `elem` as = missingmixedamt -- missingamt should always be alone, but detect it even if not
|
| missingkey `M.member` amtMap = missingmixedamt -- missingamt should always be alone, but detect it even if not
|
||||||
| null nonzeros = Mixed [newzero]
|
| M.null nonzeros= Mixed [newzero]
|
||||||
| otherwise = Mixed nonzeros
|
| otherwise = Mixed $ toList nonzeros
|
||||||
where
|
where
|
||||||
newzero = lastDef nullamt $ filter (not . T.null . acommodity) zeros
|
newzero = maybe nullamt snd . M.lookupMin $ M.filter (not . T.null . acommodity) zeros
|
||||||
(zeros, nonzeros) = partition amountIsZero $
|
(zeros, nonzeros) = M.partition amountAndPriceIsZero amtMap
|
||||||
map sumSimilarAmountsUsingFirstPrice $
|
amtMap = foldr (\a -> M.insertWith sumSimilarAmountsUsingFirstPrice (key a) a) mempty as
|
||||||
groupBy groupfn $
|
key Amount{acommodity=c,aprice=p} = (c, if squashprices then Nothing else priceKey <$> p)
|
||||||
sortBy sortfn
|
where
|
||||||
as
|
priceKey (UnitPrice x) = (acommodity x, Just $ aquantity x)
|
||||||
sortfn | squashprices = compare `on` acommodity
|
priceKey (TotalPrice x) = (acommodity x, Nothing)
|
||||||
| otherwise = compare `on` \a -> (acommodity a, aprice a)
|
missingkey = key missingamt
|
||||||
groupfn | squashprices = (==) `on` acommodity
|
|
||||||
| otherwise = \a1 a2 -> acommodity a1 == acommodity a2 && combinableprices a1 a2
|
|
||||||
|
|
||||||
combinableprices Amount{aprice=Nothing} Amount{aprice=Nothing} = True
|
|
||||||
combinableprices Amount{aprice=Just (UnitPrice p1)} Amount{aprice=Just (UnitPrice p2)} = p1 == p2
|
|
||||||
combinableprices _ _ = False
|
|
||||||
|
|
||||||
-- | Like normaliseMixedAmount, but combine each commodity's amounts
|
-- | Like normaliseMixedAmount, but combine each commodity's amounts
|
||||||
-- into just one by throwing away all prices except the first. This is
|
-- into just one by throwing away all prices except the first. This is
|
||||||
@ -600,9 +593,13 @@ unifyMixedAmount = foldM combine 0 . amounts
|
|||||||
-- | Sum same-commodity amounts in a lossy way, applying the first
|
-- | Sum same-commodity amounts in a lossy way, applying the first
|
||||||
-- price to the result and discarding any other prices. Only used as a
|
-- price to the result and discarding any other prices. Only used as a
|
||||||
-- rendering helper.
|
-- rendering helper.
|
||||||
sumSimilarAmountsUsingFirstPrice :: [Amount] -> Amount
|
sumSimilarAmountsUsingFirstPrice :: Amount -> Amount -> Amount
|
||||||
sumSimilarAmountsUsingFirstPrice [] = nullamt
|
sumSimilarAmountsUsingFirstPrice a b = (a + b){aprice=p}
|
||||||
sumSimilarAmountsUsingFirstPrice as = (sumStrict as){aprice=aprice $ head as}
|
where
|
||||||
|
p = case (aprice a, aprice b) of
|
||||||
|
(Just (TotalPrice ap), Just (TotalPrice bp))
|
||||||
|
-> Just . TotalPrice $ ap{aquantity = aquantity ap + aquantity bp }
|
||||||
|
_ -> aprice a
|
||||||
|
|
||||||
-- -- | Sum same-commodity amounts. If there were different prices, set
|
-- -- | Sum same-commodity amounts. If there were different prices, set
|
||||||
-- -- the price to a special marker indicating "various". Only used as a
|
-- -- the price to a special marker indicating "various". Only used as a
|
||||||
@ -945,9 +942,7 @@ tests_Amount = tests "Amount" [
|
|||||||
[usd 1 @@ eur 1
|
[usd 1 @@ eur 1
|
||||||
,usd (-2) @@ eur 1
|
,usd (-2) @@ eur 1
|
||||||
])
|
])
|
||||||
@?= Mixed [usd 1 @@ eur 1
|
@?= Mixed [usd (-1) @@ eur 2 ]
|
||||||
,usd (-2) @@ eur 1
|
|
||||||
]
|
|
||||||
|
|
||||||
,test "showMixedAmount" $ do
|
,test "showMixedAmount" $ do
|
||||||
showMixedAmount (Mixed [usd 1]) @?= "$1.00"
|
showMixedAmount (Mixed [usd 1]) @?= "$1.00"
|
||||||
@ -970,8 +965,8 @@ tests_Amount = tests "Amount" [
|
|||||||
normaliseMixedAmount (Mixed [usd 1 `at` eur 1, usd 1 `at` eur 1]) @?= Mixed [usd 2 `at` eur 1]
|
normaliseMixedAmount (Mixed [usd 1 `at` eur 1, usd 1 `at` eur 1]) @?= Mixed [usd 2 `at` eur 1]
|
||||||
,test "amounts with different unit prices are not combined" $
|
,test "amounts with different unit prices are not combined" $
|
||||||
normaliseMixedAmount (Mixed [usd 1 `at` eur 1, usd 1 `at` eur 2]) @?= Mixed [usd 1 `at` eur 1, usd 1 `at` eur 2]
|
normaliseMixedAmount (Mixed [usd 1 `at` eur 1, usd 1 `at` eur 2]) @?= Mixed [usd 1 `at` eur 1, usd 1 `at` eur 2]
|
||||||
,test "amounts with total prices are not combined" $
|
,test "amounts with total prices are combined" $
|
||||||
normaliseMixedAmount (Mixed [usd 1 @@ eur 1, usd 1 @@ eur 1]) @?= Mixed [usd 1 @@ eur 1, usd 1 @@ eur 1]
|
normaliseMixedAmount (Mixed [usd 1 @@ eur 1, usd 1 @@ eur 1]) @?= Mixed [usd 2 @@ eur 2]
|
||||||
]
|
]
|
||||||
|
|
||||||
,test "normaliseMixedAmountSquashPricesForDisplay" $ do
|
,test "normaliseMixedAmountSquashPricesForDisplay" $ do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user