@genome-spy/core 0.43.1 → 0.43.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/index.es.js +211 -192
- package/dist/bundle/index.js +58 -45
- package/dist/src/data/sources/lazy/bigBedSource.js +86 -23
- package/dist/src/data/transforms/coverage.d.ts +10 -2
- package/dist/src/data/transforms/coverage.d.ts.map +1 -1
- package/dist/src/data/transforms/coverage.js +22 -9
- package/dist/src/data/transforms/coverage.test.js +202 -87
- package/dist/src/utils/kWayMerge.d.ts +3 -2
- package/dist/src/utils/kWayMerge.d.ts.map +1 -1
- package/dist/src/utils/kWayMerge.js +4 -3
- package/dist/src/utils/kWayMerge.test.js +5 -1
- package/package.json +2 -2
|
@@ -92,12 +92,24 @@ export default class BigBedSource extends SingleAxisWindowedSource {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
* we have hundreds of columns having small integers (0-100).
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
95
|
+
* An optimized parser for Hautaniemi Lab's Methylation project, where
|
|
96
|
+
* we have hundreds of columns having small integers (0-100). This is over 5x
|
|
97
|
+
* faster than @gmod/bed's parser.
|
|
98
|
+
*
|
|
99
|
+
* Techniques used:
|
|
100
|
+
*
|
|
101
|
+
* 1. Avoid generating garbage by parsing integers directly from the string,
|
|
102
|
+
* i.e., without splitting the line into an array of strings.
|
|
103
|
+
* 2. Use a template object to avoid hidden class changes after each property
|
|
104
|
+
* assignment. Avoids garbage generation.
|
|
105
|
+
* 3. Generate and compile code that uses constants to access object properties,
|
|
106
|
+
* avoiding Map lookups during assignment.
|
|
107
|
+
* 4. Input chrom, startPos, and endPos as parameters so that @gmod/bbi's
|
|
108
|
+
* output doesn't first need to be converted to a string just to be parsed
|
|
109
|
+
* again.
|
|
110
|
+
*
|
|
111
|
+
* This parser doesn't support arrays, etc. at the moment. This could, however,
|
|
112
|
+
* be extended into a fully-featured parser.
|
|
101
113
|
*
|
|
102
114
|
* @param {import("@gmod/bed").default} bed
|
|
103
115
|
*/
|
|
@@ -151,26 +163,69 @@ function makeFastParser(bed) {
|
|
|
151
163
|
return value * sign;
|
|
152
164
|
}
|
|
153
165
|
|
|
166
|
+
const templateFields = fields.map(
|
|
167
|
+
(field) =>
|
|
168
|
+
`${JSON.stringify(field.name)}: ${
|
|
169
|
+
field.isNumeric ? "0" : "emptyString"
|
|
170
|
+
}`
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Make a template object with all fields to avoid the JavaScript VM's
|
|
175
|
+
* hidden class to be changed after each property assignment. Transitions
|
|
176
|
+
* between hidden classes generate plenty of garbage to be collected.
|
|
177
|
+
*
|
|
178
|
+
* Ideally, the parsed values would be assigned directly in this function,
|
|
179
|
+
* but for some reason, it results in abysmally slow performance on Chrome,
|
|
180
|
+
* but not on Firefox, where it would be super fast.
|
|
181
|
+
*/
|
|
182
|
+
const makeTemplate = new Function(`
|
|
183
|
+
const emptyString = "";
|
|
184
|
+
return function makeTemplate(chrom, chromStart, chromEnd) {
|
|
185
|
+
return {
|
|
186
|
+
chrom,
|
|
187
|
+
chromStart,
|
|
188
|
+
chromEnd,
|
|
189
|
+
${templateFields.join(",\n")}
|
|
190
|
+
}
|
|
191
|
+
};`)();
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
* Generate setter code that uses constant field names to access the
|
|
195
|
+
* object's properties. This avoids Map lookups and allows for efficient
|
|
196
|
+
* machine code to be generated by the VM.
|
|
197
|
+
*/
|
|
154
198
|
const fieldParsers = fields.map((field) => {
|
|
155
|
-
const
|
|
199
|
+
const type = field.type;
|
|
200
|
+
const name = JSON.stringify(field.name);
|
|
156
201
|
|
|
157
202
|
if (["ubyte", "int", "uint"].includes(type)) {
|
|
158
|
-
return ()
|
|
159
|
-
currentObject[name] = parseInt();
|
|
160
|
-
};
|
|
203
|
+
return `d[${name}] = parseInt();`;
|
|
161
204
|
} else if (field.isNumeric) {
|
|
162
|
-
return ()
|
|
163
|
-
currentObject[name] = Number(parseString());
|
|
164
|
-
};
|
|
205
|
+
return `d[${name}] = Number(parseString());`;
|
|
165
206
|
} else if (["char", "string", "lstring"].includes(type)) {
|
|
166
|
-
return ()
|
|
167
|
-
currentObject[name] = parseString();
|
|
168
|
-
};
|
|
207
|
+
return `d[${name}] = parseString();`;
|
|
169
208
|
} else {
|
|
170
209
|
throw new Error("Unsupported type: " + type);
|
|
210
|
+
// TODO: Implement them
|
|
171
211
|
}
|
|
172
212
|
});
|
|
173
213
|
|
|
214
|
+
/*
|
|
215
|
+
* Split the field parsers into chunks to avoid creating so large
|
|
216
|
+
* functions that the JavaScript VM would decline to optimize it.
|
|
217
|
+
* Not sure if this is really necessary, but the added cost is minimal.
|
|
218
|
+
*/
|
|
219
|
+
const chunckedFieldParsers = chunk(fieldParsers, 50).map((chunk, i) =>
|
|
220
|
+
Function(
|
|
221
|
+
"parseInt",
|
|
222
|
+
"parseString",
|
|
223
|
+
`return function parseFieldChunk${i}(d) {
|
|
224
|
+
${chunk.join("\n")}
|
|
225
|
+
}`
|
|
226
|
+
)(parseInt, parseString)
|
|
227
|
+
);
|
|
228
|
+
|
|
174
229
|
/**
|
|
175
230
|
* @param {string} line
|
|
176
231
|
*/
|
|
@@ -189,14 +244,10 @@ function makeFastParser(bed) {
|
|
|
189
244
|
function parseLine(chrom, chromStart, chromEnd, rest) {
|
|
190
245
|
setLine(rest);
|
|
191
246
|
|
|
192
|
-
currentObject =
|
|
193
|
-
chrom,
|
|
194
|
-
chromStart,
|
|
195
|
-
chromEnd,
|
|
196
|
-
};
|
|
247
|
+
currentObject = makeTemplate(chrom, chromStart, chromEnd);
|
|
197
248
|
|
|
198
|
-
for (
|
|
199
|
-
|
|
249
|
+
for (const parser of chunckedFieldParsers) {
|
|
250
|
+
parser(currentObject);
|
|
200
251
|
}
|
|
201
252
|
|
|
202
253
|
return currentObject;
|
|
@@ -204,3 +255,15 @@ function makeFastParser(bed) {
|
|
|
204
255
|
|
|
205
256
|
return parseLine;
|
|
206
257
|
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @param {T[]} arr
|
|
261
|
+
* @param {number} size
|
|
262
|
+
* @template T
|
|
263
|
+
*/
|
|
264
|
+
function chunk(arr, size) {
|
|
265
|
+
// https://www.30secondsofcode.org/js/s/split-array-into-chunks/
|
|
266
|
+
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
|
|
267
|
+
arr.slice(i * size, i * size + size)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Computes coverage for sorted segments
|
|
4
4
|
*
|
|
5
|
-
* TODO: Binned coverage
|
|
5
|
+
* TODO: Binned coverage, e.g., don't emit a new segment for every
|
|
6
|
+
* coverage change, but only every n bases or so. The most straightforward
|
|
7
|
+
* way to implement it is a separate transform that bins the coverage
|
|
8
|
+
* segments and calculates weighted averages.
|
|
6
9
|
*/
|
|
7
10
|
export default class CoverageTransform extends FlowNode {
|
|
8
11
|
/**
|
|
@@ -23,7 +26,12 @@ export default class CoverageTransform extends FlowNode {
|
|
|
23
26
|
chrom: string;
|
|
24
27
|
};
|
|
25
28
|
createSegment: Function;
|
|
26
|
-
|
|
29
|
+
/**
|
|
30
|
+
* End pos as priority, weight as value
|
|
31
|
+
*
|
|
32
|
+
* @type {FlatQueue<number>}
|
|
33
|
+
*/
|
|
34
|
+
ends: FlatQueue<number>;
|
|
27
35
|
}
|
|
28
36
|
import FlowNode from "../flowNode.js";
|
|
29
37
|
import FlatQueue from "flatqueue";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/coverage.js"],"names":[],"mappings":";AAKA
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/coverage.js"],"names":[],"mappings":";AAKA;;;;;;;GAOG;AACH;IAKI;;OAEG;IACH,oBAFW,OAAO,yBAAyB,EAAE,cAAc,EAgD1D;IA5CG,yDAAoB;IAEpB,mDAAwC;IACxC,iDAAoC;IAEpC,mCAAmC;IACnC,sBADoB,GAAG,KAAE,MAAM,CAGT;IACtB,mCAAmC;IACnC,uBADoB,GAAG,KAAE,MAAM,CACsC;IAErE;;;;;MAKC;IAGD,wBAgBC;IAED;;;;OAIG;IACH,MAFU,UAAU,MAAM,CAAC,CAEA;CAiIlC;qBAhMyC,gBAAgB;sBAHpC,WAAW"}
|
|
@@ -6,7 +6,10 @@ import FlowNode, { BEHAVIOR_CLONES } from "../flowNode.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* Computes coverage for sorted segments
|
|
8
8
|
*
|
|
9
|
-
* TODO: Binned coverage
|
|
9
|
+
* TODO: Binned coverage, e.g., don't emit a new segment for every
|
|
10
|
+
* coverage change, but only every n bases or so. The most straightforward
|
|
11
|
+
* way to implement it is a separate transform that bins the coverage
|
|
12
|
+
* segments and calculates weighted averages.
|
|
10
13
|
*/
|
|
11
14
|
export default class CoverageTransform extends FlowNode {
|
|
12
15
|
get behavior() {
|
|
@@ -56,7 +59,11 @@ export default class CoverageTransform extends FlowNode {
|
|
|
56
59
|
)
|
|
57
60
|
);
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
/**
|
|
63
|
+
* End pos as priority, weight as value
|
|
64
|
+
*
|
|
65
|
+
* @type {FlatQueue<number>}
|
|
66
|
+
*/
|
|
60
67
|
this.ends = new FlatQueue();
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -75,7 +82,7 @@ export default class CoverageTransform extends FlowNode {
|
|
|
75
82
|
const chromAccessor = this.chromAccessor;
|
|
76
83
|
const weightAccessor = this.weightAccessor;
|
|
77
84
|
|
|
78
|
-
/** @type {
|
|
85
|
+
/** @type {import("../flowNode.js").Datum} used for merging adjacent segment */
|
|
79
86
|
let bufferedSegment;
|
|
80
87
|
|
|
81
88
|
/** @type {string} */
|
|
@@ -91,7 +98,7 @@ export default class CoverageTransform extends FlowNode {
|
|
|
91
98
|
/** @type {number} */
|
|
92
99
|
let prevEdge;
|
|
93
100
|
|
|
94
|
-
|
|
101
|
+
/** End pos as priority, weight as value */
|
|
95
102
|
const ends = this.ends;
|
|
96
103
|
ends.clear();
|
|
97
104
|
|
|
@@ -127,9 +134,7 @@ export default class CoverageTransform extends FlowNode {
|
|
|
127
134
|
};
|
|
128
135
|
|
|
129
136
|
const flushQueue = () => {
|
|
130
|
-
|
|
131
|
-
/** @type {number} */
|
|
132
|
-
let edge;
|
|
137
|
+
let edge = 0;
|
|
133
138
|
while ((edge = ends.peekValue()) !== undefined) {
|
|
134
139
|
pushSegment(prevEdge, edge, coverage);
|
|
135
140
|
prevEdge = edge;
|
|
@@ -147,8 +152,7 @@ export default class CoverageTransform extends FlowNode {
|
|
|
147
152
|
this.handle = (datum) => {
|
|
148
153
|
const start = startAccessor(datum);
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
let edge;
|
|
155
|
+
let edge = 0;
|
|
152
156
|
while ((edge = ends.peekValue()) !== undefined && edge < start) {
|
|
153
157
|
pushSegment(prevEdge, edge, coverage);
|
|
154
158
|
prevEdge = edge;
|
|
@@ -179,5 +183,14 @@ export default class CoverageTransform extends FlowNode {
|
|
|
179
183
|
flushQueue();
|
|
180
184
|
super.complete();
|
|
181
185
|
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {import("../../types/flowBatch.js").FlowBatch} flowBatch
|
|
189
|
+
*/
|
|
190
|
+
this.beginBatch = (flowBatch) => {
|
|
191
|
+
flushQueue();
|
|
192
|
+
prevChrom = undefined;
|
|
193
|
+
super.beginBatch(flowBatch);
|
|
194
|
+
};
|
|
182
195
|
}
|
|
183
196
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, test } from "vitest";
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
2
|
import CoverageTransform from "./coverage.js";
|
|
3
3
|
import { processData } from "../flowTestUtils.js";
|
|
4
4
|
|
|
@@ -16,108 +16,223 @@ function transform(params, data) {
|
|
|
16
16
|
return processData(t, data);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
[8, 10],
|
|
26
|
-
[11, 14],
|
|
27
|
-
[11, 13],
|
|
28
|
-
[11, 12],
|
|
29
|
-
[15, 18],
|
|
30
|
-
[16, 18],
|
|
31
|
-
[17, 18],
|
|
32
|
-
].map((d) => ({
|
|
33
|
-
start: d[0],
|
|
34
|
-
end: d[1],
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
const coverageSegments = [
|
|
38
|
-
[0, 1, 1],
|
|
39
|
-
[1, 2, 2],
|
|
40
|
-
[2, 3, 3],
|
|
41
|
-
[3, 6, 2],
|
|
42
|
-
[6, 10, 1],
|
|
43
|
-
[11, 12, 3],
|
|
44
|
-
[12, 13, 2],
|
|
45
|
-
[13, 14, 1],
|
|
46
|
-
[15, 16, 1],
|
|
47
|
-
[16, 17, 2],
|
|
48
|
-
[17, 18, 3],
|
|
49
|
-
].map((d) => ({
|
|
50
|
-
start: d[0],
|
|
51
|
-
end: d[1],
|
|
52
|
-
coverage: d[2],
|
|
53
|
-
}));
|
|
54
|
-
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {[number, number][]} reads Start, end
|
|
22
|
+
* @param {[number, number, number][]} coverageSegments Start, end, coverage
|
|
23
|
+
*/
|
|
24
|
+
function testSimpleCoverage(reads, coverageSegments) {
|
|
55
25
|
/** @type {CoverageParams} */
|
|
56
26
|
const coverageConfig = {
|
|
57
27
|
type: "coverage",
|
|
58
28
|
start: "start",
|
|
59
29
|
end: "end",
|
|
60
30
|
};
|
|
61
|
-
expect(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
31
|
+
expect(
|
|
32
|
+
transform(
|
|
33
|
+
coverageConfig,
|
|
34
|
+
reads.map((d) => ({
|
|
35
|
+
start: d[0],
|
|
36
|
+
end: d[1],
|
|
37
|
+
}))
|
|
38
|
+
)
|
|
39
|
+
).toEqual(
|
|
40
|
+
coverageSegments.map((d) => ({
|
|
41
|
+
start: d[0],
|
|
42
|
+
end: d[1],
|
|
43
|
+
coverage: d[2],
|
|
44
|
+
}))
|
|
45
|
+
);
|
|
46
|
+
}
|
|
76
47
|
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param {[number, number, number][]} reads Start, end, weight
|
|
51
|
+
* @param {[number, number, number][]} coverageSemgments Start, end, coverage
|
|
52
|
+
*/
|
|
53
|
+
function testWeightedCoverage(reads, coverageSegments) {
|
|
77
54
|
/** @type {CoverageParams} */
|
|
78
55
|
const coverageConfig = {
|
|
79
56
|
type: "coverage",
|
|
80
|
-
chrom: "chrom",
|
|
81
57
|
start: "start",
|
|
82
58
|
end: "end",
|
|
59
|
+
weight: "weight",
|
|
83
60
|
};
|
|
61
|
+
expect(
|
|
62
|
+
transform(
|
|
63
|
+
coverageConfig,
|
|
64
|
+
reads.map((d) => ({
|
|
65
|
+
start: d[0],
|
|
66
|
+
end: d[1],
|
|
67
|
+
weight: d[2],
|
|
68
|
+
}))
|
|
69
|
+
)
|
|
70
|
+
).toEqual(
|
|
71
|
+
coverageSegments.map((d) => ({
|
|
72
|
+
start: d[0],
|
|
73
|
+
end: d[1],
|
|
74
|
+
coverage: d[2],
|
|
75
|
+
}))
|
|
76
|
+
);
|
|
77
|
+
}
|
|
84
78
|
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
describe("Coverage transform", () => {
|
|
80
|
+
test("Typical case", () =>
|
|
81
|
+
testSimpleCoverage(
|
|
82
|
+
[
|
|
83
|
+
[0, 4],
|
|
84
|
+
[1, 3],
|
|
85
|
+
[2, 6],
|
|
86
|
+
[4, 8],
|
|
87
|
+
[8, 10],
|
|
88
|
+
[11, 14],
|
|
89
|
+
[11, 13],
|
|
90
|
+
[11, 12],
|
|
91
|
+
[15, 18],
|
|
92
|
+
[16, 18],
|
|
93
|
+
[17, 18],
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
[0, 1, 1],
|
|
97
|
+
[1, 2, 2],
|
|
98
|
+
[2, 3, 3],
|
|
99
|
+
[3, 6, 2],
|
|
100
|
+
[6, 10, 1],
|
|
101
|
+
[11, 12, 3],
|
|
102
|
+
[12, 13, 2],
|
|
103
|
+
[13, 14, 1],
|
|
104
|
+
[15, 16, 1],
|
|
105
|
+
[16, 17, 2],
|
|
106
|
+
[17, 18, 3],
|
|
107
|
+
]
|
|
108
|
+
));
|
|
87
109
|
|
|
88
|
-
test("
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
test("Multiple identical overlapping segments", () =>
|
|
111
|
+
testSimpleCoverage(
|
|
112
|
+
[
|
|
113
|
+
[1, 2],
|
|
114
|
+
[3, 4],
|
|
115
|
+
[3, 4],
|
|
116
|
+
[5, 6],
|
|
117
|
+
[5, 6],
|
|
118
|
+
[5, 6],
|
|
119
|
+
],
|
|
120
|
+
[
|
|
121
|
+
[1, 2, 1],
|
|
122
|
+
[3, 4, 2],
|
|
123
|
+
[5, 6, 3],
|
|
124
|
+
]
|
|
125
|
+
));
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
127
|
+
test("Adjacent segments with equal coverage are merged", () =>
|
|
128
|
+
testSimpleCoverage(
|
|
129
|
+
[
|
|
130
|
+
[1, 2],
|
|
131
|
+
[2, 3],
|
|
132
|
+
[3, 4],
|
|
133
|
+
[5, 6],
|
|
134
|
+
[6, 7],
|
|
135
|
+
[7, 8],
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
[1, 4, 1],
|
|
139
|
+
[5, 8, 1],
|
|
140
|
+
]
|
|
141
|
+
));
|
|
112
142
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
143
|
+
test("Chromosomes pass through", () => {
|
|
144
|
+
const reads = [
|
|
145
|
+
{ chrom: "chr1", start: 0, end: 1 },
|
|
146
|
+
{ chrom: "chr2", start: 0, end: 1 },
|
|
147
|
+
{ chrom: "chr3", start: 1, end: 3 },
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const coverageSegments = [
|
|
151
|
+
{ chrom: "chr1", start: 0, end: 1, coverage: 1 },
|
|
152
|
+
{ chrom: "chr2", start: 0, end: 1, coverage: 1 },
|
|
153
|
+
{ chrom: "chr3", start: 1, end: 3, coverage: 1 },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
/** @type {CoverageParams} */
|
|
157
|
+
const coverageConfig = {
|
|
158
|
+
type: "coverage",
|
|
159
|
+
chrom: "chrom",
|
|
160
|
+
start: "start",
|
|
161
|
+
end: "end",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
expect(transform(coverageConfig, reads)).toEqual(coverageSegments);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("Typical weighted coverage", () =>
|
|
168
|
+
testWeightedCoverage(
|
|
169
|
+
[
|
|
170
|
+
[0, 4, 1],
|
|
171
|
+
[1, 3, 2],
|
|
172
|
+
[2, 6, 3],
|
|
173
|
+
[8, 10, -1],
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
[0, 1, 1],
|
|
177
|
+
[1, 2, 3],
|
|
178
|
+
[2, 3, 6],
|
|
179
|
+
[3, 4, 4],
|
|
180
|
+
[4, 6, 3],
|
|
181
|
+
[8, 10, -1],
|
|
182
|
+
]
|
|
183
|
+
));
|
|
184
|
+
|
|
185
|
+
test("Multiple weights at a single locus", () =>
|
|
186
|
+
testWeightedCoverage(
|
|
187
|
+
[
|
|
188
|
+
// -- Locus 1
|
|
189
|
+
[1, 2, 1],
|
|
190
|
+
[1, 2, 2],
|
|
191
|
+
[1, 2, 3],
|
|
192
|
+
[1, 2, 4],
|
|
193
|
+
[1, 2, 5],
|
|
194
|
+
// -- Locus 2
|
|
195
|
+
[2, 3, 2],
|
|
196
|
+
[2, 3, 3],
|
|
197
|
+
[2, 3, 4],
|
|
198
|
+
[2, 3, 5],
|
|
199
|
+
[2, 3, 6],
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
// -- Locus 1
|
|
203
|
+
[1, 2, 15],
|
|
204
|
+
// -- Locus 2
|
|
205
|
+
[2, 3, 20],
|
|
206
|
+
]
|
|
207
|
+
));
|
|
121
208
|
|
|
122
|
-
|
|
209
|
+
test("Adjacent segments with different weights produce separated segments", () =>
|
|
210
|
+
testWeightedCoverage(
|
|
211
|
+
[
|
|
212
|
+
// -- Cluster 1
|
|
213
|
+
[1, 2, 2],
|
|
214
|
+
[2, 3, 1],
|
|
215
|
+
[3, 4, 1],
|
|
216
|
+
// -- Cluster 2
|
|
217
|
+
[5, 6, 1],
|
|
218
|
+
[6, 7, 2],
|
|
219
|
+
[7, 8, 1],
|
|
220
|
+
// -- Cluster 3
|
|
221
|
+
[9, 10, 1],
|
|
222
|
+
[10, 11, 1],
|
|
223
|
+
[11, 12, 2],
|
|
224
|
+
],
|
|
225
|
+
[
|
|
226
|
+
// -- Cluster 1
|
|
227
|
+
[1, 2, 2],
|
|
228
|
+
[2, 4, 1],
|
|
229
|
+
// -- Cluster 2
|
|
230
|
+
[5, 6, 1],
|
|
231
|
+
[6, 7, 2],
|
|
232
|
+
[7, 8, 1],
|
|
233
|
+
// -- Cluster 3
|
|
234
|
+
[9, 11, 1],
|
|
235
|
+
[11, 12, 2],
|
|
236
|
+
]
|
|
237
|
+
));
|
|
123
238
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Merges multiple sorted arrays.
|
|
3
3
|
*
|
|
4
4
|
* @param {T[][]} arrays
|
|
5
|
+
* @param {function(T):void} handler a function that will be called for each element
|
|
5
6
|
* @param {function(T):number} [accessor]
|
|
6
7
|
* @template T
|
|
7
8
|
*/
|
|
8
|
-
export default function kWayMerge<T>(arrays: T[][], accessor?: (arg0: T) => number):
|
|
9
|
+
export default function kWayMerge<T>(arrays: T[][], handler: (arg0: T) => void, accessor?: (arg0: T) => number): void;
|
|
9
10
|
//# sourceMappingURL=kWayMerge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kWayMerge.d.ts","sourceRoot":"","sources":["../../../src/utils/kWayMerge.js"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"kWayMerge.d.ts","sourceRoot":"","sources":["../../../src/utils/kWayMerge.js"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,0EAJuB,IAAI,0BACJ,MAAM,QAmC5B"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import FlatQueue from "flatqueue";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Merges multiple sorted arrays.
|
|
5
5
|
*
|
|
6
6
|
* @param {T[][]} arrays
|
|
7
|
+
* @param {function(T):void} handler a function that will be called for each element
|
|
7
8
|
* @param {function(T):number} [accessor]
|
|
8
9
|
* @template T
|
|
9
10
|
*/
|
|
10
|
-
export default function
|
|
11
|
+
export default function kWayMerge(arrays, handler, accessor = (x) => +x) {
|
|
11
12
|
// https://www.wikiwand.com/en/K-way_merge_algorithm
|
|
12
13
|
|
|
13
14
|
// This could be optimized by implementing a tournament tree or
|
|
@@ -31,7 +32,7 @@ export default function* kWayMerge(arrays, accessor = (x) => +x) {
|
|
|
31
32
|
let pointer = pointers[i];
|
|
32
33
|
const element = array[pointer++];
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
handler(element);
|
|
35
36
|
|
|
36
37
|
if (pointer < array.length) {
|
|
37
38
|
const newValue = accessor(array[pointer]);
|
|
@@ -22,5 +22,9 @@ test("k-way merge merges multiple sorted arrays", () => {
|
|
|
22
22
|
/** @type {function(any):number} */
|
|
23
23
|
const accessor = (d) => d.a;
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/** @type {{a: number}[]} */
|
|
26
|
+
const result = [];
|
|
27
|
+
kWayMerge(arrays, (d) => result.push(d), accessor);
|
|
28
|
+
|
|
29
|
+
expect(result).toEqual(sorted);
|
|
26
30
|
});
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"contributors": [],
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"version": "0.43.
|
|
10
|
+
"version": "0.43.3",
|
|
11
11
|
"jsdelivr": "dist/bundle/index.js",
|
|
12
12
|
"unpkg": "dist/bundle/index.js",
|
|
13
13
|
"browser": "dist/bundle/index.js",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"vega-scale": "^7.1.1",
|
|
66
66
|
"vega-util": "^1.16.0"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "0c6e0418a7461b40da98896bfaf5e05732384e85"
|
|
69
69
|
}
|