John, thanks very much for taking the time to write this up. As you know this is one the areas I've burned the most cycles trying to make it 100% compatible and robust. Knowledge of this recipe should help considerably in my next crack at it.

I think you may have seen this, but http://paste.lisp.org/display/ 75617 shows an alternate balance report format which I think my clients will find easier to read, and which also may be easier to implement. I'll probably be adding this one as an option.


On Feb 17, 2009, at 1:43 PM, John Wiegley wrote:

Simon,

Since you've been re-implementing the balance report in Haskell, I thought I'd share with you in pseudocode how I rewrote it for Ledger 3.0, since the old method never stopped with the bugs. The new scheme uses a 5 stage algorithm, with each stage gathering information for the next:

STEP 1

Based on the user's query, walk through all the transactions in their journal, finding which ones to include in the account subtotals. For each transaction that matches, mark the account as VISITED.

STEP 2

Recursively walk the accounts tree, depth-first, computing aggregate totals and aggregate "counts" (number of transactions contributing to the aggregate total).

STEP 3

Walk the account tree again, breadth-first, and for every VISITED account, check whether it matches the user's "display predicate". If so, mark the account as MATCHING.

STEP 4

Do an in-order traversal of the account tree. Except for the top- most account (which serves strictly as a container for the other accounts):

a. If the account was MATCHING, or two or more of its children are
  MATCHING or had descendents who were MATCHING, display the account.

b. Otherwise, if the account had *any* children or descendants who
  were VISITED and *no* children or descendants who were MATCHING,
  then apply the display predicate from STEP 3 to the account.  If
  it matches, also print this account.  (This step allows -E to
  report empty accounts which otherwise did match the original
  query).

STEP 5

When printing an account, display a "depth spacer" followed by the "partial name".

The partial name is found by taking the base account's name, then prepending to it every non-MATCHING parent until a MATCHING parent is found.

The depth spacer is found by outputting two spaces for every MATCHING parent.

This way, "Assets:Bank:Checking" might be reported as:

Assets
  Bank
    Checking

or

Assets
  Bank:Checking

or

Assets:Bank:Checking

Depending on whether the parents were or were not reported for their own reasons.

John

Reply via email to