StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
8.4 KiB
365 lines
8.4 KiB
"use strict"
|
|
|
|
var bounds = require("binary-search-bounds")
|
|
|
|
var NOT_FOUND = 0
|
|
var SUCCESS = 1
|
|
var EMPTY = 2
|
|
|
|
module.exports = createWrapper
|
|
|
|
function IntervalTreeNode(mid, left, right, leftPoints, rightPoints) {
|
|
this.mid = mid
|
|
this.left = left
|
|
this.right = right
|
|
this.leftPoints = leftPoints
|
|
this.rightPoints = rightPoints
|
|
this.count = (left ? left.count : 0) + (right ? right.count : 0) + leftPoints.length
|
|
}
|
|
|
|
var proto = IntervalTreeNode.prototype
|
|
|
|
function copy(a, b) {
|
|
a.mid = b.mid
|
|
a.left = b.left
|
|
a.right = b.right
|
|
a.leftPoints = b.leftPoints
|
|
a.rightPoints = b.rightPoints
|
|
a.count = b.count
|
|
}
|
|
|
|
function rebuild(node, intervals) {
|
|
var ntree = createIntervalTree(intervals)
|
|
node.mid = ntree.mid
|
|
node.left = ntree.left
|
|
node.right = ntree.right
|
|
node.leftPoints = ntree.leftPoints
|
|
node.rightPoints = ntree.rightPoints
|
|
node.count = ntree.count
|
|
}
|
|
|
|
function rebuildWithInterval(node, interval) {
|
|
var intervals = node.intervals([])
|
|
intervals.push(interval)
|
|
rebuild(node, intervals)
|
|
}
|
|
|
|
function rebuildWithoutInterval(node, interval) {
|
|
var intervals = node.intervals([])
|
|
var idx = intervals.indexOf(interval)
|
|
if(idx < 0) {
|
|
return NOT_FOUND
|
|
}
|
|
intervals.splice(idx, 1)
|
|
rebuild(node, intervals)
|
|
return SUCCESS
|
|
}
|
|
|
|
proto.intervals = function(result) {
|
|
result.push.apply(result, this.leftPoints)
|
|
if(this.left) {
|
|
this.left.intervals(result)
|
|
}
|
|
if(this.right) {
|
|
this.right.intervals(result)
|
|
}
|
|
return result
|
|
}
|
|
|
|
proto.insert = function(interval) {
|
|
var weight = this.count - this.leftPoints.length
|
|
this.count += 1
|
|
if(interval[1] < this.mid) {
|
|
if(this.left) {
|
|
if(4*(this.left.count+1) > 3*(weight+1)) {
|
|
rebuildWithInterval(this, interval)
|
|
} else {
|
|
this.left.insert(interval)
|
|
}
|
|
} else {
|
|
this.left = createIntervalTree([interval])
|
|
}
|
|
} else if(interval[0] > this.mid) {
|
|
if(this.right) {
|
|
if(4*(this.right.count+1) > 3*(weight+1)) {
|
|
rebuildWithInterval(this, interval)
|
|
} else {
|
|
this.right.insert(interval)
|
|
}
|
|
} else {
|
|
this.right = createIntervalTree([interval])
|
|
}
|
|
} else {
|
|
var l = bounds.ge(this.leftPoints, interval, compareBegin)
|
|
var r = bounds.ge(this.rightPoints, interval, compareEnd)
|
|
this.leftPoints.splice(l, 0, interval)
|
|
this.rightPoints.splice(r, 0, interval)
|
|
}
|
|
}
|
|
|
|
proto.remove = function(interval) {
|
|
var weight = this.count - this.leftPoints
|
|
if(interval[1] < this.mid) {
|
|
if(!this.left) {
|
|
return NOT_FOUND
|
|
}
|
|
var rw = this.right ? this.right.count : 0
|
|
if(4 * rw > 3 * (weight-1)) {
|
|
return rebuildWithoutInterval(this, interval)
|
|
}
|
|
var r = this.left.remove(interval)
|
|
if(r === EMPTY) {
|
|
this.left = null
|
|
this.count -= 1
|
|
return SUCCESS
|
|
} else if(r === SUCCESS) {
|
|
this.count -= 1
|
|
}
|
|
return r
|
|
} else if(interval[0] > this.mid) {
|
|
if(!this.right) {
|
|
return NOT_FOUND
|
|
}
|
|
var lw = this.left ? this.left.count : 0
|
|
if(4 * lw > 3 * (weight-1)) {
|
|
return rebuildWithoutInterval(this, interval)
|
|
}
|
|
var r = this.right.remove(interval)
|
|
if(r === EMPTY) {
|
|
this.right = null
|
|
this.count -= 1
|
|
return SUCCESS
|
|
} else if(r === SUCCESS) {
|
|
this.count -= 1
|
|
}
|
|
return r
|
|
} else {
|
|
if(this.count === 1) {
|
|
if(this.leftPoints[0] === interval) {
|
|
return EMPTY
|
|
} else {
|
|
return NOT_FOUND
|
|
}
|
|
}
|
|
if(this.leftPoints.length === 1 && this.leftPoints[0] === interval) {
|
|
if(this.left && this.right) {
|
|
var p = this
|
|
var n = this.left
|
|
while(n.right) {
|
|
p = n
|
|
n = n.right
|
|
}
|
|
if(p === this) {
|
|
n.right = this.right
|
|
} else {
|
|
var l = this.left
|
|
var r = this.right
|
|
p.count -= n.count
|
|
p.right = n.left
|
|
n.left = l
|
|
n.right = r
|
|
}
|
|
copy(this, n)
|
|
this.count = (this.left?this.left.count:0) + (this.right?this.right.count:0) + this.leftPoints.length
|
|
} else if(this.left) {
|
|
copy(this, this.left)
|
|
} else {
|
|
copy(this, this.right)
|
|
}
|
|
return SUCCESS
|
|
}
|
|
for(var l = bounds.ge(this.leftPoints, interval, compareBegin); l<this.leftPoints.length; ++l) {
|
|
if(this.leftPoints[l][0] !== interval[0]) {
|
|
break
|
|
}
|
|
if(this.leftPoints[l] === interval) {
|
|
this.count -= 1
|
|
this.leftPoints.splice(l, 1)
|
|
for(var r = bounds.ge(this.rightPoints, interval, compareEnd); r<this.rightPoints.length; ++r) {
|
|
if(this.rightPoints[r][1] !== interval[1]) {
|
|
break
|
|
} else if(this.rightPoints[r] === interval) {
|
|
this.rightPoints.splice(r, 1)
|
|
return SUCCESS
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NOT_FOUND
|
|
}
|
|
}
|
|
|
|
function reportLeftRange(arr, hi, cb) {
|
|
for(var i=0; i<arr.length && arr[i][0] <= hi; ++i) {
|
|
var r = cb(arr[i])
|
|
if(r) { return r }
|
|
}
|
|
}
|
|
|
|
function reportRightRange(arr, lo, cb) {
|
|
for(var i=arr.length-1; i>=0 && arr[i][1] >= lo; --i) {
|
|
var r = cb(arr[i])
|
|
if(r) { return r }
|
|
}
|
|
}
|
|
|
|
function reportRange(arr, cb) {
|
|
for(var i=0; i<arr.length; ++i) {
|
|
var r = cb(arr[i])
|
|
if(r) { return r }
|
|
}
|
|
}
|
|
|
|
proto.queryPoint = function(x, cb) {
|
|
if(x < this.mid) {
|
|
if(this.left) {
|
|
var r = this.left.queryPoint(x, cb)
|
|
if(r) { return r }
|
|
}
|
|
return reportLeftRange(this.leftPoints, x, cb)
|
|
} else if(x > this.mid) {
|
|
if(this.right) {
|
|
var r = this.right.queryPoint(x, cb)
|
|
if(r) { return r }
|
|
}
|
|
return reportRightRange(this.rightPoints, x, cb)
|
|
} else {
|
|
return reportRange(this.leftPoints, cb)
|
|
}
|
|
}
|
|
|
|
proto.queryInterval = function(lo, hi, cb) {
|
|
if(lo < this.mid && this.left) {
|
|
var r = this.left.queryInterval(lo, hi, cb)
|
|
if(r) { return r }
|
|
}
|
|
if(hi > this.mid && this.right) {
|
|
var r = this.right.queryInterval(lo, hi, cb)
|
|
if(r) { return r }
|
|
}
|
|
if(hi < this.mid) {
|
|
return reportLeftRange(this.leftPoints, hi, cb)
|
|
} else if(lo > this.mid) {
|
|
return reportRightRange(this.rightPoints, lo, cb)
|
|
} else {
|
|
return reportRange(this.leftPoints, cb)
|
|
}
|
|
}
|
|
|
|
function compareNumbers(a, b) {
|
|
return a - b
|
|
}
|
|
|
|
function compareBegin(a, b) {
|
|
var d = a[0] - b[0]
|
|
if(d) { return d }
|
|
return a[1] - b[1]
|
|
}
|
|
|
|
function compareEnd(a, b) {
|
|
var d = a[1] - b[1]
|
|
if(d) { return d }
|
|
return a[0] - b[0]
|
|
}
|
|
|
|
function createIntervalTree(intervals) {
|
|
if(intervals.length === 0) {
|
|
return null
|
|
}
|
|
var pts = []
|
|
for(var i=0; i<intervals.length; ++i) {
|
|
pts.push(intervals[i][0], intervals[i][1])
|
|
}
|
|
pts.sort(compareNumbers)
|
|
|
|
var mid = pts[pts.length>>1]
|
|
|
|
var leftIntervals = []
|
|
var rightIntervals = []
|
|
var centerIntervals = []
|
|
for(var i=0; i<intervals.length; ++i) {
|
|
var s = intervals[i]
|
|
if(s[1] < mid) {
|
|
leftIntervals.push(s)
|
|
} else if(mid < s[0]) {
|
|
rightIntervals.push(s)
|
|
} else {
|
|
centerIntervals.push(s)
|
|
}
|
|
}
|
|
|
|
//Split center intervals
|
|
var leftPoints = centerIntervals
|
|
var rightPoints = centerIntervals.slice()
|
|
leftPoints.sort(compareBegin)
|
|
rightPoints.sort(compareEnd)
|
|
|
|
return new IntervalTreeNode(mid,
|
|
createIntervalTree(leftIntervals),
|
|
createIntervalTree(rightIntervals),
|
|
leftPoints,
|
|
rightPoints)
|
|
}
|
|
|
|
//User friendly wrapper that makes it possible to support empty trees
|
|
function IntervalTree(root) {
|
|
this.root = root
|
|
}
|
|
|
|
var tproto = IntervalTree.prototype
|
|
|
|
tproto.insert = function(interval) {
|
|
if(this.root) {
|
|
this.root.insert(interval)
|
|
} else {
|
|
this.root = new IntervalTreeNode(interval[0], null, null, [interval], [interval])
|
|
}
|
|
}
|
|
|
|
tproto.remove = function(interval) {
|
|
if(this.root) {
|
|
var r = this.root.remove(interval)
|
|
if(r === EMPTY) {
|
|
this.root = null
|
|
}
|
|
return r !== NOT_FOUND
|
|
}
|
|
return false
|
|
}
|
|
|
|
tproto.queryPoint = function(p, cb) {
|
|
if(this.root) {
|
|
return this.root.queryPoint(p, cb)
|
|
}
|
|
}
|
|
|
|
tproto.queryInterval = function(lo, hi, cb) {
|
|
if(lo <= hi && this.root) {
|
|
return this.root.queryInterval(lo, hi, cb)
|
|
}
|
|
}
|
|
|
|
Object.defineProperty(tproto, "count", {
|
|
get: function() {
|
|
if(this.root) {
|
|
return this.root.count
|
|
}
|
|
return 0
|
|
}
|
|
})
|
|
|
|
Object.defineProperty(tproto, "intervals", {
|
|
get: function() {
|
|
if(this.root) {
|
|
return this.root.intervals([])
|
|
}
|
|
return []
|
|
}
|
|
})
|
|
|
|
function createWrapper(intervals) {
|
|
if(!intervals || intervals.length === 0) {
|
|
return new IntervalTree(null)
|
|
}
|
|
return new IntervalTree(createIntervalTree(intervals))
|
|
}
|
|
|