Gathering detailed insights and metrics for diff-sequences
Gathering detailed insights and metrics for diff-sequences
Gathering detailed insights and metrics for diff-sequences
Gathering detailed insights and metrics for diff-sequences
@jsdotlua/diff-sequences
@kingjs/linq.except
Generates the set difference of two sequences.
@datagrok/sequence-translator
SequenceTranslator translates [oligonucleotide](https://en.wikipedia.org/wiki/Oligonucleotide) sequences between [different representations](https://github.com/datagrok-ai/public/tree/master/packages/SequenceTranslator#sequence-representations).
@hutechwebsite/voluptates-illum-cupiditate-quos
A little function that formats an error object as a nice, readable string. Works in node and the browser; in node, it will use [kleur](https://www.npmjs.com/package/kleur) to add ANSI color code escape sequences to the output string, to make it easier to
Delightful JavaScript Testing.
npm install diff-sequences
Typescript
Module System
Min. Node Version
Node Version
NPM Version
99.9
Supply Chain
99.5
Quality
84.2
Maintenance
100
Vulnerability
100
License
v30.0.0-alpha.6
Published on 08 Aug 2024
v30.0.0-alpha.5
Published on 30 May 2024
v30.0.0-alpha.4
Published on 12 May 2024
v30.0.0-alpha.3
Published on 20 Feb 2024
v30.0.0-alpha.2
Published on 16 Nov 2023
v30.0.0-alpha.1
Published on 30 Oct 2023
Updated on 06 Dec 2024
Minified
Minified + Gzipped
TypeScript (79.22%)
JavaScript (20.12%)
CSS (0.57%)
Prolog (0.08%)
Shell (0.01%)
Handlebars (0.01%)
Cumulative downloads
Total Downloads
Last day
11.2%
Compared to previous day
Last week
-10.1%
Compared to previous week
Last month
14.5%
Compared to previous month
Last year
11.2%
Compared to previous year
3
Compare items in two sequences to find a longest common subsequence.
The items not in common are the items to delete or insert in a shortest edit script.
To maximize flexibility and minimize memory, you write callback functions as configuration:
Input function isCommon(aIndex, bIndex)
compares items at indexes in the sequences and returns a truthy/falsey value. This package might call your function more than once for some pairs of indexes.
===
operator, Object.is
method, or other criterion.Output function foundSubsequence(nCommon, aCommon, bCommon)
receives the number of adjacent items and starting indexes of each common subsequence. If sequences do not have common items, then this package does not call your function.
If N is the sum of lengths of sequences and L is length of a longest common subsequence, then D = N – 2L is the number of differences in the corresponding shortest edit script.
An O(ND) Difference Algorithm and Its Variations by Eugene W. Myers is fast when sequences have few differences.
This package implements the linear space variation with optimizations so it is fast even when sequences have many differences.
To add this package as a dependency of a project, do either of the following:
npm install diff-sequences
yarn add diff-sequences
To use diff
as the name of the default export from this package, do either of the following:
var diff = require('diff-sequences').default; // CommonJS modules
import diff from 'diff-sequences'; // ECMAScript modules
Call diff
with the lengths of sequences and your callback functions:
1const a = ['a', 'b', 'c', 'a', 'b', 'b', 'a']; 2const b = ['c', 'b', 'a', 'b', 'a', 'c']; 3 4function isCommon(aIndex, bIndex) { 5 return a[aIndex] === b[bIndex]; 6} 7function foundSubsequence(nCommon, aCommon, bCommon) { 8 // see examples 9} 10 11diff(a.length, b.length, isCommon, foundSubsequence);
Some sequences (for example, a
and b
in the example of usage) have more than one longest common subsequence.
This package finds the following common items:
comparisons of common items | values | output arguments |
---|---|---|
a[2] === b[0] | 'c' | foundSubsequence(1, 2, 0) |
a[4] === b[1] | 'b' | foundSubsequence(1, 4, 1) |
a[5] === b[3] && a[6] === b[4] | 'b', 'a' | foundSubsequence(2, 5, 3) |
The “edit graph” analogy in the Myers paper shows the following common items:
comparisons of common items | values |
---|---|
a[2] === b[0] | 'c' |
a[3] === b[2] && a[4] === b[3] | 'a', 'b' |
a[6] === b[4] | 'a' |
Various packages which implement the Myers algorithm will always agree on the length of a longest common subsequence, but might sometimes disagree on which items are in it.
1// Return length of longest common subsequence according to === operator. 2function countCommonItems(a, b) { 3 let n = 0; 4 function isCommon(aIndex, bIndex) { 5 return a[aIndex] === b[bIndex]; 6 } 7 function foundSubsequence(nCommon) { 8 n += nCommon; 9 } 10 11 diff(a.length, b.length, isCommon, foundSubsequence); 12 13 return n; 14} 15 16const commonLength = countCommonItems( 17 ['a', 'b', 'c', 'a', 'b', 'b', 'a'], 18 ['c', 'b', 'a', 'b', 'a', 'c'], 19);
category of items | expression | value |
---|---|---|
in common | commonLength | 4 |
to delete from a | a.length - commonLength | 3 |
to insert from b | b.length - commonLength | 2 |
If the length difference b.length - a.length
is:
a
b
a
and insert from b
a
and insert from b
In this example, 6 - 7
is:
1
is the minimum number of items to delete from a
2
is the number of additional items to delete from a
and insert from b
1// Return array of items in longest common subsequence according to Object.is method. 2const findCommonItems = (a, b) => { 3 const array = []; 4 diff( 5 a.length, 6 b.length, 7 (aIndex, bIndex) => Object.is(a[aIndex], b[bIndex]), 8 (nCommon, aCommon) => { 9 for (; nCommon !== 0; nCommon -= 1, aCommon += 1) { 10 array.push(a[aCommon]); 11 } 12 }, 13 ); 14 return array; 15}; 16 17const commonItems = findCommonItems( 18 ['a', 'b', 'c', 'a', 'b', 'b', 'a'], 19 ['c', 'b', 'a', 'b', 'a', 'c'], 20);
i | commonItems[i] | aIndex |
---|---|---|
0 | 'c' | 2 |
1 | 'b' | 4 |
2 | 'b' | 5 |
3 | 'a' | 6 |
Instead of slicing array-like objects, you can adjust indexes in your callback functions.
1// Diff index intervals that are half open [start, end) like array slice method. 2const diffIndexIntervals = (a, aStart, aEnd, b, bStart, bEnd) => { 3 // Validate: 0 <= aStart and aStart <= aEnd and aEnd <= a.length 4 // Validate: 0 <= bStart and bStart <= bEnd and bEnd <= b.length 5 6 diff( 7 aEnd - aStart, 8 bEnd - bStart, 9 (aIndex, bIndex) => Object.is(a[aStart + aIndex], b[bStart + bIndex]), 10 (nCommon, aCommon, bCommon) => { 11 // aStart + aCommon, bStart + bCommon 12 }, 13 ); 14 15 // After the last common subsequence, do any remaining work. 16};
Linux or Unix has a diff
command to compare files line by line. Its output is a shortest edit script:
1// Given zero-based half-open range [start, end) of array indexes, 2// return one-based closed range [start + 1, end] as string. 3const getRange = (start, end) => 4 start + 1 === end ? `${start + 1}` : `${start + 1},${end}`; 5 6// Given index intervals of lines to delete or insert, or both, or neither, 7// push formatted diff lines onto array. 8const pushDelIns = (aLines, aIndex, aEnd, bLines, bIndex, bEnd, array) => { 9 const deleteLines = aIndex !== aEnd; 10 const insertLines = bIndex !== bEnd; 11 const changeLines = deleteLines && insertLines; 12 if (changeLines) { 13 array.push(`${getRange(aIndex, aEnd)}c${getRange(bIndex, bEnd)}`); 14 } else if (deleteLines) { 15 array.push(`${getRange(aIndex, aEnd)}d${String(bIndex)}`); 16 } else if (insertLines) { 17 array.push(`${String(aIndex)}a${getRange(bIndex, bEnd)}`); 18 } else { 19 return; 20 } 21 22 for (; aIndex !== aEnd; aIndex += 1) { 23 array.push(`< ${aLines[aIndex]}`); // delete is less than 24 } 25 26 if (changeLines) { 27 array.push('---'); 28 } 29 30 for (; bIndex !== bEnd; bIndex += 1) { 31 array.push(`> ${bLines[bIndex]}`); // insert is greater than 32 } 33}; 34 35// Given content of two files, return emulated output of diff utility. 36const findShortestEditScript = (a, b) => { 37 const aLines = a.split('\n'); 38 const bLines = b.split('\n'); 39 const aLength = aLines.length; 40 const bLength = bLines.length; 41 42 const isCommon = (aIndex, bIndex) => aLines[aIndex] === bLines[bIndex]; 43 44 let aIndex = 0; 45 let bIndex = 0; 46 const array = []; 47 const foundSubsequence = (nCommon, aCommon, bCommon) => { 48 pushDelIns(aLines, aIndex, aCommon, bLines, bIndex, bCommon, array); 49 aIndex = aCommon + nCommon; // number of lines compared in a 50 bIndex = bCommon + nCommon; // number of lines compared in b 51 }; 52 53 diff(aLength, bLength, isCommon, foundSubsequence); 54 55 // After the last common subsequence, push remaining change lines. 56 pushDelIns(aLines, aIndex, aLength, bLines, bIndex, bLength, array); 57 58 return array.length === 0 ? '' : `${array.join('\n')}\n`; 59};
Here is simplified code to format changed and unchanged lines in expected and received values after a test fails in Jest:
1// Format diff with minus or plus for change lines and space for common lines. 2const formatDiffLines = (a, b) => { 3 // Jest depends on pretty-format package to serialize objects as strings. 4 // Unindented for comparison to avoid distracting differences: 5 const aLinesUn = format(a, {indent: 0 /*, other options*/}).split('\n'); 6 const bLinesUn = format(b, {indent: 0 /*, other options*/}).split('\n'); 7 // Indented to display changed and unchanged lines: 8 const aLinesIn = format(a, {indent: 2 /*, other options*/}).split('\n'); 9 const bLinesIn = format(b, {indent: 2 /*, other options*/}).split('\n'); 10 11 const aLength = aLinesIn.length; // Validate: aLinesUn.length === aLength 12 const bLength = bLinesIn.length; // Validate: bLinesUn.length === bLength 13 14 const isCommon = (aIndex, bIndex) => aLinesUn[aIndex] === bLinesUn[bIndex]; 15 16 // Only because the GitHub Flavored Markdown doc collapses adjacent spaces, 17 // this example code and the following table represent spaces as middle dots. 18 let aIndex = 0; 19 let bIndex = 0; 20 const array = []; 21 const foundSubsequence = (nCommon, aCommon, bCommon) => { 22 for (; aIndex !== aCommon; aIndex += 1) { 23 array.push(`-·${aLinesIn[aIndex]}`); // delete is minus 24 } 25 for (; bIndex !== bCommon; bIndex += 1) { 26 array.push(`+·${bLinesIn[bIndex]}`); // insert is plus 27 } 28 for (; nCommon !== 0; nCommon -= 1, aIndex += 1, bIndex += 1) { 29 // For common lines, received indentation seems more intuitive. 30 array.push(`··${bLinesIn[bIndex]}`); // common is space 31 } 32 }; 33 34 diff(aLength, bLength, isCommon, foundSubsequence); 35 36 // After the last common subsequence, push remaining change lines. 37 for (; aIndex !== aLength; aIndex += 1) { 38 array.push(`-·${aLinesIn[aIndex]}`); 39 } 40 for (; bIndex !== bLength; bIndex += 1) { 41 array.push(`+·${bLinesIn[bIndex]}`); 42 } 43 44 return array; 45}; 46 47const expected = { 48 searching: '', 49 sorting: { 50 ascending: true, 51 fieldKey: 'what', 52 }, 53}; 54const received = { 55 searching: '', 56 sorting: [ 57 { 58 descending: false, 59 fieldKey: 'what', 60 }, 61 ], 62}; 63 64const diffLines = formatDiffLines(expected, received);
If N is the sum of lengths of sequences and L is length of a longest common subsequence, then N – L is length of an array of diff lines. In this example, N is 7 + 9, L is 5, and N – L is 11.
i | diffLines[i] | aIndex | bIndex |
---|---|---|---|
0 | '··Object {' | 0 | 0 |
1 | '····"searching": "",' | 1 | 1 |
2 | '-···"sorting": Object {' | 2 | |
3 | '-·····"ascending": true,' | 3 | |
4 | '+·····"sorting": Array [' | 2 | |
5 | '+·······Object {' | 3 | |
6 | '+·········"descending": false,' | 4 | |
7 | '··········"fieldKey": "what",' | 4 | 5 |
8 | '········},' | 5 | 6 |
9 | '+·····],' | 7 | |
10 | '··}' | 6 | 8 |
Here is simplified code to find changed and unchanged substrings within adjacent changed lines in expected and received values after a test fails in Jest:
1// Return diff items for strings (compatible with diff-match-patch package). 2const findDiffItems = (a, b) => { 3 const isCommon = (aIndex, bIndex) => a[aIndex] === b[bIndex]; 4 5 let aIndex = 0; 6 let bIndex = 0; 7 const array = []; 8 const foundSubsequence = (nCommon, aCommon, bCommon) => { 9 if (aIndex !== aCommon) { 10 array.push([-1, a.slice(aIndex, aCommon)]); // delete is -1 11 } 12 if (bIndex !== bCommon) { 13 array.push([1, b.slice(bIndex, bCommon)]); // insert is 1 14 } 15 16 aIndex = aCommon + nCommon; // number of characters compared in a 17 bIndex = bCommon + nCommon; // number of characters compared in b 18 array.push([0, a.slice(aCommon, aIndex)]); // common is 0 19 }; 20 21 diff(a.length, b.length, isCommon, foundSubsequence); 22 23 // After the last common subsequence, push remaining change items. 24 if (aIndex !== a.length) { 25 array.push([-1, a.slice(aIndex)]); 26 } 27 if (bIndex !== b.length) { 28 array.push([1, b.slice(bIndex)]); 29 } 30 31 return array; 32}; 33 34const expectedDeleted = ['"sorting": Object {', '"ascending": true,'].join( 35 '\n', 36); 37const receivedInserted = [ 38 '"sorting": Array [', 39 'Object {', 40 '"descending": false,', 41].join('\n'); 42 43const diffItems = findDiffItems(expectedDeleted, receivedInserted);
i | diffItems[i][0] | diffItems[i][1] |
---|---|---|
0 | 0 | '"sorting": ' |
1 | 1 | 'Array [\n' |
2 | 0 | 'Object {\n"' |
3 | -1 | 'a' |
4 | 1 | 'de' |
5 | 0 | 'scending": ' |
6 | -1 | 'tru' |
7 | 1 | 'fals' |
8 | 0 | 'e,' |
The length difference b.length - a.length
is equal to the sum of diffItems[i][0]
values times diffItems[i][1]
lengths. In this example, the difference 48 - 38
is equal to the sum 10
.
category of diff item | [0] | [1] lengths | subtotal |
---|---|---|---|
in common | 0 | 11 + 10 + 11 + 2 | 0 |
to delete from a | –1 | 1 + 3 | -4 |
to insert from b | 1 | 8 + 2 + 4 | 14 |
Instead of formatting the changed substrings with escape codes for colors in the foundSubsequence
function to save memory, this example spends memory to gain flexibility before formatting, so a separate heuristic algorithm might modify the generic array of diff items to show changes more clearly:
i | diffItems[i][0] | diffItems[i][1] |
---|---|---|
6 | -1 | 'true' |
7 | 1 | 'false' |
8 | 0 | ',' |
For expected and received strings of serialized data, the result of finding changed lines, and then finding changed substrings within adjacent changed lines (as in the preceding two examples) sometimes displays the changes in a more intuitive way than the result of finding changed substrings, and then splitting them into changed and unchanged lines.
No vulnerabilities found.
No security vulnerabilities found.