(a: string, b: string)
| 40 | |
| 41 | /** Classic LCS line diff → unified-style lines (' ctx' / '- old' / '+ new'). PURE. */ |
| 42 | export function lineDiff(a: string, b: string): string[] { |
| 43 | const A = a.split('\n'), B = b.split('\n'); |
| 44 | const n = A.length, m = B.length; |
| 45 | // LCS table (texts here are small — worklog entries, not files). |
| 46 | const L: number[][] = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); |
| 47 | for (let i = n - 1; i >= 0; i--) { |
| 48 | for (let j = m - 1; j >= 0; j--) { |
| 49 | L[i]![j] = A[i] === B[j] ? L[i + 1]![j + 1]! + 1 : Math.max(L[i + 1]![j]!, L[i]![j + 1]!); |
| 50 | } |
| 51 | } |
| 52 | const out: string[] = []; |
| 53 | let i = 0, j = 0; |
| 54 | while (i < n && j < m) { |
| 55 | if (A[i] === B[j]) { out.push(` ${A[i]}`); i++; j++; } |
| 56 | else if (L[i + 1]![j]! >= L[i]![j + 1]!) { out.push(`- ${A[i]}`); i++; } |
| 57 | else { out.push(`+ ${B[j]}`); j++; } |
| 58 | } |
| 59 | while (i < n) out.push(`- ${A[i++]}`); |
| 60 | while (j < m) out.push(`+ ${B[j++]}`); |
| 61 | return out; |
| 62 | } |
| 63 | |
| 64 | /** Terms (surface forms) whose stem appears in EVERY text — the stable core of the approach. PURE. */ |
| 65 | export function commonCore(texts: string[], cap = 8): string[] { |
no test coverage detected