| /* |
| * Copyright © 2003 Davide Libenzi |
| * 2018 Benjamin Otte |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * Authors: Davide Libenzi <davidel@xmailserver.org> |
| * Benjamin Otte <otte@gnome.org> |
| */ |
| |
| #include "config.h" |
| |
| #include "gskdiffprivate.h" |
| |
| |
| #define XDL_MAX_COST_MIN 256 |
| #define XDL_HEUR_MIN_COST 256 |
| #define XDL_LINE_MAX G_MAXSSIZE |
| #define XDL_SNAKE_CNT 20 |
| #define XDL_K_HEUR 4 |
| #define MAXCOST 20 |
| |
| struct _GskDiffSettings { |
| GCompareDataFunc compare_func; |
| GskKeepFunc keep_func; |
| GskDeleteFunc delete_func; |
| GskInsertFunc insert_func; |
| |
| guint allow_abort : 1; |
| }; |
| |
| typedef struct _SplitResult { |
| long i1, i2; |
| int min_lo, min_hi; |
| } SplitResult; |
| |
| GskDiffSettings * |
| gsk_diff_settings_new (GCompareDataFunc compare_func, |
| GskKeepFunc keep_func, |
| GskDeleteFunc delete_func, |
| GskInsertFunc insert_func) |
| { |
| GskDiffSettings *settings; |
| |
| settings = g_new0 (GskDiffSettings, 1); |
| |
| settings->compare_func = compare_func; |
| settings->keep_func = keep_func; |
| settings->delete_func = delete_func; |
| settings->insert_func = insert_func; |
| |
| return settings; |
| } |
| |
| void |
| gsk_diff_settings_set_allow_abort (GskDiffSettings *settings, |
| gboolean allow_abort) |
| { |
| settings->allow_abort = allow_abort; |
| } |
| |
| void |
| gsk_diff_settings_free (GskDiffSettings *settings) |
| { |
| g_free (settings); |
| } |
| |
| /* |
| * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. |
| * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both |
| * the forward diagonal starting from (off1, off2) and the backward diagonal |
| * starting from (lim1, lim2). If the K values on the same diagonal crosses |
| * returns the furthest point of reach. We might end up having to expensive |
| * cases using this algorithm is full, so a little bit of heuristic is needed |
| * to cut the search and to return a suboptimal point. |
| */ |
| static GskDiffResult |
| split (gconstpointer *elem1, |
| gssize off1, |
| gssize lim1, |
| gconstpointer *elem2, |
| gssize off2, |
| gssize lim2, |
| gssize *kvdf, |
| gssize *kvdb, |
| gboolean need_min, |
| const GskDiffSettings *settings, |
| gpointer data, |
| SplitResult *spl) |
| { |
| gssize dmin = off1 - lim2, dmax = lim1 - off2; |
| gssize fmid = off1 - off2, bmid = lim1 - lim2; |
| gboolean odd = (fmid - bmid) & 1; |
| gssize fmin = fmid, fmax = fmid; |
| gssize bmin = bmid, bmax = bmid; |
| gssize ec, d, i1, i2, prev1, best, dd, v, k; |
| |
| /* |
| * Set initial diagonal values for both forward and backward path. |
| */ |
| kvdf[fmid] = off1; |
| kvdb[bmid] = lim1; |
| |
| for (ec = 1;; ec++) |
| { |
| gboolean got_snake = FALSE; |
| |
| /* |
| * We need to extent the diagonal "domain" by one. If the next |
| * values exits the box boundaries we need to change it in the |
| * opposite direction because (max - min) must be a power of two. |
| * Also we initialize the external K value to -1 so that we can |
| * avoid extra conditions check inside the core loop. |
| */ |
| if (fmin > dmin) |
| kvdf[--fmin - 1] = -1; |
| else |
| ++fmin; |
| if (fmax < dmax) |
| kvdf[++fmax + 1] = -1; |
| else |
| --fmax; |
| |
| for (d = fmax; d >= fmin; d -= 2) |
| { |
| if (kvdf[d - 1] >= kvdf[d + 1]) |
| i1 = kvdf[d - 1] + 1; |
| else |
| i1 = kvdf[d + 1]; |
| prev1 = i1; |
| i2 = i1 - d; |
| for (; i1 < lim1 && i2 < lim2; i1++, i2++) |
| { |
| if (settings->compare_func (elem1[i1], elem2[i2], data) != 0) |
| break; |
| } |
| if (i1 - prev1 > XDL_SNAKE_CNT) |
| got_snake = TRUE; |
| kvdf[d] = i1; |
| if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) |
| { |
| spl->i1 = i1; |
| spl->i2 = i2; |
| spl->min_lo = spl->min_hi = 1; |
| return GSK_DIFF_OK; |
| } |
| } |
| |
| /* |
| * We need to extent the diagonal "domain" by one. If the next |
| * values exits the box boundaries we need to change it in the |
| * opposite direction because (max - min) must be a power of two. |
| * Also we initialize the external K value to -1 so that we can |
| * avoid extra conditions check inside the core loop. |
| */ |
| if (bmin > dmin) |
| kvdb[--bmin - 1] = XDL_LINE_MAX; |
| else |
| ++bmin; |
| if (bmax < dmax) |
| kvdb[++bmax + 1] = XDL_LINE_MAX; |
| else |
| --bmax; |
| |
| for (d = bmax; d >= bmin; d -= 2) |
| { |
| if (kvdb[d - 1] < kvdb[d + 1]) |
| i1 = kvdb[d - 1]; |
| else |
| i1 = kvdb[d + 1] - 1; |
| prev1 = i1; |
| i2 = i1 - d; |
| for (; i1 > off1 && i2 > off2; i1--, i2--) |
| { |
| if (settings->compare_func (elem1[i1 - 1], elem2[i2 - 1], data) != 0) |
| break; |
| } |
| if (prev1 - i1 > XDL_SNAKE_CNT) |
| got_snake = TRUE; |
| kvdb[d] = i1; |
| if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) |
| { |
| spl->i1 = i1; |
| spl->i2 = i2; |
| spl->min_lo = spl->min_hi = 1; |
| return GSK_DIFF_OK; |
| } |
| } |
| |
| if (need_min) |
| continue; |
| |
| /* |
| * If the edit cost is above the heuristic trigger and if |
| * we got a good snake, we sample current diagonals to see |
| * if some of them have reached an "interesting" path. Our |
| * measure is a function of the distance from the diagonal |
| * corner (i1 + i2) penalized with the distance from the |
| * mid diagonal itself. If this value is above the current |
| * edit cost times a magic factor (XDL_K_HEUR) we consider |
| * it interesting. |
| */ |
| if (got_snake && ec > XDL_HEUR_MIN_COST) |
| { |
| for (best = 0, d = fmax; d >= fmin; d -= 2) |
| { |
| dd = d > fmid ? d - fmid: fmid - d; |
| i1 = kvdf[d]; |
| i2 = i1 - d; |
| v = (i1 - off1) + (i2 - off2) - dd; |
| |
| if (v > XDL_K_HEUR * ec && v > best && |
| off1 + XDL_SNAKE_CNT <= i1 && i1 < lim1 && |
| off2 + XDL_SNAKE_CNT <= i2 && i2 < lim2) |
| { |
| for (k = 1; ; k++) |
| { |
| if (settings->compare_func (elem1[i1 - k], elem2[i2 - k], data) != 0) |
| break; |
| if (k == XDL_SNAKE_CNT) |
| { |
| best = v; |
| spl->i1 = i1; |
| spl->i2 = i2; |
| break; |
| } |
| } |
| } |
| } |
| if (best > 0) |
| { |
| spl->min_lo = 1; |
| spl->min_hi = 0; |
| return GSK_DIFF_OK; |
| } |
| |
| for (best = 0, d = bmax; d >= bmin; d -= 2) |
| { |
| dd = d > bmid ? d - bmid: bmid - d; |
| i1 = kvdb[d]; |
| i2 = i1 - d; |
| v = (lim1 - i1) + (lim2 - i2) - dd; |
| |
| if (v > XDL_K_HEUR * ec && v > best && |
| off1 < i1 && i1 <= lim1 - XDL_SNAKE_CNT && |
| off2 < i2 && i2 <= lim2 - XDL_SNAKE_CNT) |
| { |
| for (k = 0; ; k++) |
| { |
| if (settings->compare_func (elem1[i1 + k], elem2[i2 + k], data) != 0) |
| break; |
| |
| if (k == XDL_SNAKE_CNT - 1) |
| { |
| best = v; |
| spl->i1 = i1; |
| spl->i2 = i2; |
| break; |
| } |
| } |
| } |
| } |
| if (best > 0) |
| { |
| spl->min_lo = 0; |
| spl->min_hi = 1; |
| return GSK_DIFF_OK; |
| } |
| } |
| |
| /* |
| * Enough is enough. We spent too much time here and now we collect |
| * the furthest reaching path using the (i1 + i2) measure. |
| */ |
| if (ec >= MAXCOST) |
| { |
| gssize fbest, fbest1, bbest, bbest1; |
| |
| if (settings->allow_abort) |
| return GSK_DIFF_ABORTED; |
| |
| fbest = fbest1 = -1; |
| for (d = fmax; d >= fmin; d -= 2) |
| { |
| i1 = MIN (kvdf[d], lim1); |
| i2 = i1 - d; |
| if (lim2 < i2) |
| i1 = lim2 + d, i2 = lim2; |
| if (fbest < i1 + i2) |
| { |
| fbest = i1 + i2; |
| fbest1 = i1; |
| } |
| } |
| |
| bbest = bbest1 = XDL_LINE_MAX; |
| for (d = bmax; d >= bmin; d -= 2) |
| { |
| i1 = MAX (off1, kvdb[d]); |
| i2 = i1 - d; |
| if (i2 < off2) |
| i1 = off2 + d, i2 = off2; |
| if (i1 + i2 < bbest) |
| { |
| bbest = i1 + i2; |
| bbest1 = i1; |
| } |
| } |
| |
| if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) |
| { |
| spl->i1 = fbest1; |
| spl->i2 = fbest - fbest1; |
| spl->min_lo = 1; |
| spl->min_hi = 0; |
| } |
| else |
| { |
| spl->i1 = bbest1; |
| spl->i2 = bbest - bbest1; |
| spl->min_lo = 0; |
| spl->min_hi = 1; |
| } |
| |
| return GSK_DIFF_OK; |
| } |
| } |
| } |
| |
| /* |
| * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling |
| * the box splitting function. Note that the real job (marking changed lines) |
| * is done in the two boundary reaching checks. |
| */ |
| static GskDiffResult |
| compare (gconstpointer *elem1, |
| gssize off1, |
| gssize lim1, |
| gconstpointer *elem2, |
| gssize off2, |
| gssize lim2, |
| gssize *kvdf, |
| gssize *kvdb, |
| gboolean need_min, |
| const GskDiffSettings *settings, |
| gpointer data) |
| { |
| GskDiffResult res; |
| |
| /* |
| * Shrink the box by walking through each diagonal snake (SW and NE). |
| */ |
| for (; off1 < lim1 && off2 < lim2; off1++, off2++) |
| { |
| if (settings->compare_func (elem1[off1], elem2[off2], data) != 0) |
| break; |
| |
| res = settings->keep_func (elem1[off1], elem2[off2], data); |
| if (res != GSK_DIFF_OK) |
| return res; |
| } |
| |
| for (; off1 < lim1 && off2 < lim2; lim1--, lim2--) |
| { |
| if (settings->compare_func (elem1[lim1 - 1], elem2[lim2 - 1], data) != 0) |
| break; |
| |
| res = settings->keep_func (elem1[lim1 - 1], elem2[lim2 - 1], data); |
| if (res != GSK_DIFF_OK) |
| return res; |
| } |
| |
| /* |
| * If one dimension is empty, then all records on the other one must |
| * be obviously changed. |
| */ |
| if (off1 == lim1) |
| { |
| for (; off2 < lim2; off2++) |
| { |
| res = settings->insert_func (elem2[off2], off2, data); |
| if (res != GSK_DIFF_OK) |
| return res; |
| } |
| } |
| else if (off2 == lim2) |
| { |
| for (; off1 < lim1; off1++) |
| { |
| res = settings->delete_func (elem1[off1], off1, data); |
| if (res != GSK_DIFF_OK) |
| return res; |
| } |
| } |
| else |
| { |
| SplitResult spl = { 0, }; |
| |
| /* |
| * Divide ... |
| */ |
| res = split (elem1, off1, lim1, |
| elem2, off2, lim2, |
| kvdf, kvdb, need_min, |
| settings, data, |
| &spl); |
| if (res != GSK_DIFF_OK) |
| return res; |
| |
| /* |
| * ... et Impera. |
| */ |
| res = compare (elem1, off1, spl.i1, |
| elem2, off2, spl.i2, |
| kvdf, kvdb, spl.min_lo, |
| settings, data); |
| if (res != GSK_DIFF_OK) |
| return res; |
| res = compare (elem1, spl.i1, lim1, |
| elem2, spl.i2, lim2, |
| kvdf, kvdb, spl.min_hi, |
| settings, data); |
| if (res != GSK_DIFF_OK) |
| return res; |
| } |
| |
| return GSK_DIFF_OK; |
| } |
| |
| #if 0 |
| ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; |
| if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { |
| |
| xdl_free_env(xe); |
| return -1; |
| } |
| kvdf = kvd; |
| kvdb = kvdf + ndiags; |
| kvdf += xe->xdf2.nreff + 1; |
| kvdb += xe->xdf2.nreff + 1; |
| |
| xenv.mxcost = xdl_bogosqrt(ndiags); |
| if (xenv.mxcost < XDL_MAX_COST_MIN) |
| xenv.mxcost = XDL_MAX_COST_MIN; |
| xenv.snake_cnt = XDL_SNAKE_CNT; |
| xenv.heur_min = XDL_HEUR_MIN_COST; |
| |
| dd1.nrec = xe->xdf1.nreff; |
| dd1.ha = xe->xdf1.ha; |
| dd1.rchg = xe->xdf1.rchg; |
| dd1.rindex = xe->xdf1.rindex; |
| dd2.nrec = xe->xdf2.nreff; |
| dd2.ha = xe->xdf2.ha; |
| dd2.rchg = xe->xdf2.rchg; |
| dd2.rindex = xe->xdf2.rindex; |
| #endif |
| |
| GskDiffResult |
| gsk_diff (gconstpointer *elem1, |
| gsize n1, |
| gconstpointer *elem2, |
| gsize n2, |
| const GskDiffSettings *settings, |
| gpointer data) |
| { |
| gsize ndiags; |
| gssize *kvd, *kvdf, *kvdb; |
| GskDiffResult res; |
| |
| ndiags = n1 + n2 + 3; |
| |
| kvd = g_new (gssize, 2 * ndiags + 2); |
| kvdf = kvd; |
| kvdb = kvd + ndiags; |
| kvdf += n2 + 1; |
| kvdb += n2 + 1; |
| |
| res = compare (elem1, 0, n1, |
| elem2, 0, n2, |
| kvdf, kvdb, FALSE, |
| settings, data); |
| |
| g_free (kvd); |
| |
| return res; |
| } |
| |