From ff759c26bcebef18f0327d3eef80a21d0d20e252 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sun, 20 Apr 2025 23:21:13 +0800 Subject: [PATCH] [engine.i] convert gnc_account_accumulate_at_dates to c++ traversal of account->splits through std::vector instead of scm_list --- bindings/engine.i | 61 +++++++++++++++++++++++++++++ gnucash/report/report-utilities.scm | 38 ++---------------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/bindings/engine.i b/bindings/engine.i index 4b7adbafe7..bdc4d1abac 100644 --- a/bindings/engine.i +++ b/bindings/engine.i @@ -66,6 +66,22 @@ using AccountVec = std::vector; SplitsVec gnc_get_match_commodity_splits (AccountVec accounts, bool use_end_date, time64 end_date, gnc_commodity *comm, bool sort); +/* scans account splits, in posted date order, calling split_fn(split) + at each split and accumulate the result at each date in the SCM + list dates into a new SCM list. Parameters are: + + acc the account + dates the SCM list of posted dates, assumed to be in chronological order + init the result to be pushed into the result for dates prior to first split date + split_fn the split->elt procedure whose result of (split_fn split) will be pushed + into the returned SCM list */ +SCM gnc_account_accumulate_to_dates (const Account *acc, SCM dates, + SCM split_fn, SCM init); + +/* as above, but the split_to_date function to use a different date order. */ +SCM gnc_account_accumulate_to_dates (const Account *acc, SCM dates, + SCM split_fn, SCM init, SCM split_to_date); + AccountVec gnc_accounts_and_all_descendants (AccountVec accounts); extern "C" @@ -160,6 +176,51 @@ SplitsVec gnc_get_match_commodity_splits (AccountVec accounts, bool use_end_date return rv; } +static SCM +accumulate_splits_by_dates (const SplitsVec& splits, SCM dates, SCM result, + std::function no_later_than_date, + std::function get_result) +{ + SCM rv = SCM_EOL; + auto splits_it = splits.begin(); + for (; !scm_is_null(dates); dates = scm_cdr (dates)) + { + while (splits_it != splits.end() && no_later_than_date (*splits_it, scm_car (dates))) + result = get_result (*splits_it++); + + rv = scm_cons (result, rv); + } + return scm_reverse_x (rv, SCM_EOL); +} + +SCM +gnc_account_accumulate_to_dates (const Account *acc, SCM dates, + SCM split_fn, SCM init) +{ + const auto& splits = xaccAccountGetSplits(acc); + auto get_result = [&](Split* s) -> SCM { return scm_call_1(split_fn, gnc_split_to_scm(s)); }; + auto no_later_than_date = [&](Split* s, SCM date) -> bool + { return xaccTransGetDate (xaccSplitGetParent (s)) <= scm_to_int64 (date); }; + + return accumulate_splits_by_dates (splits, dates, init, no_later_than_date, get_result); +} + +SCM +gnc_account_accumulate_to_dates (const Account *acc, SCM dates, + SCM split_fn, SCM init, SCM split_to_date) +{ + auto splits = xaccAccountGetSplits(acc); + auto less_scm = [](SCM a, SCM b) -> bool { return scm_is_true(scm_less_p(a, b)); }; + auto get_date = [&](Split* s) -> SCM { return scm_call_1(split_to_date, gnc_split_to_scm(s)); }; + auto get_result = [&](Split* s) -> SCM { return scm_call_1(split_fn, gnc_split_to_scm(s)); }; + auto no_later_than_date = [&](auto s, SCM date) -> bool { return !less_scm(date, get_date(s)); }; + std::sort(splits.begin(), splits.end(), [&](auto a, auto b) -> bool + { return less_scm(get_date(a), get_date(b)); }); + + return accumulate_splits_by_dates (splits, dates, init, no_later_than_date, get_result); +} + + using AccountSet = std::unordered_set; static void maybe_add_descendants (Account* acc, AccountSet* accset) { diff --git a/gnucash/report/report-utilities.scm b/gnucash/report/report-utilities.scm index a1b90ff5e5..7a39221f59 100644 --- a/gnucash/report/report-utilities.scm +++ b/gnucash/report/report-utilities.scm @@ -476,41 +476,9 @@ (nosplit->elt #f) (split->date #f) (split->elt xaccSplitGetBalance)) - (define to-date (or split->date (compose xaccTransGetDate xaccSplitGetParent))) - (define (less? a b) (< (to-date a) (to-date b))) - - (let lp ((splits (if split->date - (sort (xaccAccountGetSplits acc) less?) - (xaccAccountGetSplits acc))) - (dates (sort dates <)) - (result '()) - (last-result nosplit->elt)) - (match dates - - ;; end of dates. job done! - (() (reverse result)) - - ((date . rest) - (define (before-date? s) (<= (to-date s) date)) - (define (after-date? s) (< date (to-date s))) - (cond - - ;; end of splits, but still has dates. pad with last-result - ;; until end of dates. - ((null? splits) (lp '() rest (cons last-result result) last-result)) - - ;; the next split is still before date. - ((and (pair? (cdr splits)) (before-date? (cadr splits))) - (lp (cdr splits) dates result (split->elt (car splits)))) - - ;; head split after date, accumulate previous result - ((after-date? (car splits)) - (lp splits rest (cons last-result result) last-result)) - - ;; head split before date, next split after date, or end. - (else - (let ((head-result (split->elt (car splits)))) - (lp (cdr splits) rest (cons head-result result) head-result)))))))) + (if split->date + (gnc-account-accumulate-to-dates acc dates split->elt nosplit->elt split->date) + (gnc-account-accumulate-to-dates acc dates split->elt nosplit->elt))) ;; This works similar as above but returns a commodity-collector, ;; thus takes care of children accounts with different currencies.