gnucash stable: [gnc-autoclear.cpp] prune search if remaining amounts are too small

Christopher Lam clam at code.gnucash.org
Mon Nov 17 06:40:58 EST 2025


Updated	 via  https://github.com/Gnucash/gnucash/commit/7c73e2ab (commit)
	from  https://github.com/Gnucash/gnucash/commit/e4d61b30 (commit)



commit 7c73e2ab2c757177c3143bfb9035e2e47fd9120f
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Nov 16 11:53:37 2025 +0800

    [gnc-autoclear.cpp] prune search if remaining amounts are too small
    
    this optimisation will greatly reduce the search size. first, sort
    splits by reverse absolute amount. then precompute the positive and
    negative sum(amounts) of remaining splits. when exploring any branch,
    if the target is falls outside the range remaining negative to
    positive sums, then any combination of subsequent splits will not
    reach target. bail early.

diff --git a/libgnucash/app-utils/gnc-autoclear.cpp b/libgnucash/app-utils/gnc-autoclear.cpp
index 2a31f5f01b..11fd4d58a4 100644
--- a/libgnucash/app-utils/gnc-autoclear.cpp
+++ b/libgnucash/app-utils/gnc-autoclear.cpp
@@ -69,6 +69,8 @@ struct RuntimeMonitor
 struct SplitInfo
 {
     int64_t m_amount;
+    int64_t m_rem_pos;
+    int64_t m_rem_neg;
     Split* m_split;
 };
 
@@ -111,8 +113,11 @@ subset_sum (SplitInfoVec::const_iterator iter,
             SplitInfoVec& path, Solution& solution,
             int64_t target, RuntimeMonitor& monitor)
 {
-    DEBUG ("this=%" PRId64" target=%" PRId64 " path=%s",
-           iter == end ? 0 : iter->m_amount, target, path_to_str (path));
+    DEBUG ("this=%" PRId64" target=%" PRId64 " rem_pos=%" PRId64
+           " rem_neg=%" PRId64 " path=%s",
+           iter == end ? 0 : iter->m_amount, target,
+           iter == end ? 0 : iter->m_rem_pos,
+           iter == end ? 0 : iter->m_rem_neg, path_to_str (path));
 
     if (target == 0)
     {
@@ -139,6 +144,13 @@ subset_sum (SplitInfoVec::const_iterator iter,
         return;
     }
 
+    if (target < iter->m_rem_neg || target > iter->m_rem_pos)
+    {
+        DEBUG ("PRUNE target=%" PRId64 " rem_pos=%" PRId64" rem_neg=%" PRId64,
+               target, iter->m_rem_pos, iter->m_rem_neg);
+        return;
+    }
+
     auto next = std::next(iter);
 
     // 1st path: use current_num
@@ -177,7 +189,7 @@ gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
         else if (amt == 0)
             DEBUG ("skipping zero-amount split %p", split);
         else
-            splits.push_back ({amt, split});
+            splits.push_back ({amt, 0, 0, split});
     }
 
     static GQuark autoclear_quark = g_quark_from_static_string ("autoclear");
@@ -188,6 +200,24 @@ gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
         return nullptr;
     }
 
+    // sort splits in descending absolute amount
+    std::sort (splits.begin(), splits.end(),
+               [](const SplitInfo& a, const SplitInfo& b)
+               {
+                   int64_t aa = std::llabs(a.m_amount);
+                   int64_t bb = std::llabs(b.m_amount);
+                   return (aa == bb) ? a.m_amount > b.m_amount : aa > bb;
+               });
+
+    // for each split, precompute sums of remaining pos or neg amounts
+    int64_t rem_pos{0}, rem_neg{0};
+    std::for_each (splits.rbegin(), splits.rend(),
+                   [&rem_pos, &rem_neg](SplitInfo& s)
+                   {
+                       s.m_rem_pos = rem_pos += std::max<int64_t>(s.m_amount, 0);
+                       s.m_rem_neg = rem_neg += std::min<int64_t>(s.m_amount, 0);
+                   });
+
     RuntimeMonitor monitor{max_seconds};
     Solution solution;
     SplitInfoVec path;



Summary of changes:
 libgnucash/app-utils/gnc-autoclear.cpp | 36 +++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)



More information about the gnucash-changes mailing list