Skip to content

Commit 44a0c3d

Browse files
committed
plotly#189 switching from O(n) to O(log(N)) complexity unique insertion sort
1 parent c6d44d9 commit 44a0c3d

File tree

1 file changed

+27
-15
lines changed

1 file changed

+27
-15
lines changed

src/plots/cartesian/ordered_categories.js

+27-15
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,44 @@
1111

1212
var d3 = require('d3');
1313

14-
// flattenUnique :: String -> [[String]] -> Object
15-
function flattenUnique(axisLetter, data) {
16-
var traceLines = data.map(function(d) {return d[axisLetter];});
14+
// flattenUniqueSort :: String -> Function -> [[String]] -> [String]
15+
function flattenUniqueSort(axisLetter, sortFunction, data) {
16+
17+
// Bisection based insertion sort of distinct values for logarithmic time complexity.
1718
// Can't use a hashmap, which is O(1), because ES5 maps coerce keys to strings. If it ever becomes a bottleneck,
1819
// code can be separated: a hashmap (JS object) based version if all values encountered are strings; and
19-
// downgrading to this O(n) array on the first encounter of a non-string value.
20-
// Another possible speedup is bisection, but it's probably slower on the small array
21-
// sizes typical of categorical axis values.
20+
// downgrading to this O(log(n)) array on the first encounter of a non-string value.
21+
2222
var categoryArray = [];
23-
var i, j, tracePoints, category;
23+
24+
var traceLines = data.map(function(d) {return d[axisLetter];});
25+
26+
var i, j, tracePoints, category, insertionIndex;
27+
28+
var bisector = d3.bisector(sortFunction).left;
29+
2430
for(i = 0; i < traceLines.length; i++) {
31+
2532
tracePoints = traceLines[i];
33+
2634
for(j = 0; j < tracePoints.length; j++) {
35+
2736
category = tracePoints[j];
37+
38+
// skip loop: ignore null and undefined categories
2839
if(category === null || category === undefined) continue;
29-
if(categoryArray.indexOf(category) === -1) {
30-
categoryArray.push(category);
31-
}
40+
41+
insertionIndex = bisector(categoryArray, category);
42+
43+
// skip loop on already encountered values
44+
if(insertionIndex < categoryArray.length - 1 && categoryArray[insertionIndex] === category) continue;
45+
46+
// insert value
47+
categoryArray.splice(insertionIndex, 0, category);
3248
}
3349
}
34-
return categoryArray;
35-
}
3650

37-
// flattenUniqueSort :: String -> Function -> [[String]] -> [String]
38-
function flattenUniqueSort(axisLetter, sortFunction, data) {
39-
return flattenUnique(axisLetter, data).sort(sortFunction);
51+
return categoryArray;
4052
}
4153

4254

0 commit comments

Comments
 (0)