|
// setOps.js MIT License © 2014 James Abney http://github.com/jabney |
|
// async.js modification © 2015 Tobias Rittig http://github.com/diiigle |
|
|
|
// Set operations union, intersection, symmetric difference, |
|
// relative complement, equals. Set operations are fast. |
|
(function() { |
|
'use strict'; |
|
var so = {}; |
|
|
|
// global on the server, window in the browser |
|
var previous_setOps; |
|
|
|
// Establish the root object, `window` (`self`) in the browser, `global` |
|
// on the server, or `this` in some virtual machines. We use `self` |
|
// instead of `window` for `WebWorker` support. |
|
var root = typeof self === 'object' && self.self === self && self || |
|
typeof global === 'object' && global.global === global && global || |
|
this; |
|
|
|
if (root != null) { |
|
previous_setOps = root.setOps; |
|
} |
|
|
|
so.noConflict = function () { |
|
root.setOps = previous_setOps; |
|
return so; |
|
}; |
|
|
|
//get async.js, window.async in the browser, via require on the server |
|
var async = root != null && root.async || typeof require == 'function' && require('async'); |
|
|
|
var uidList = [], uid; |
|
|
|
// Create and push the uid identity method. |
|
uidList.push(uid = function() { |
|
return this; |
|
}); |
|
|
|
// Push a new uid method onto the stack. Call this and |
|
// supply a unique key generator for sets of objects. |
|
so.pushUid = function(method) { |
|
uidList.push(method); |
|
uid = method; |
|
return method; |
|
}; |
|
|
|
// Pop the previously pushed uid method off the stack and |
|
// assign top of stack to uid. Return the previous method. |
|
so.popUid = function() { |
|
var prev; |
|
uidList.length > 1 && (prev = uidList.pop()); |
|
uid = uidList[uidList.length-1]; |
|
return prev || null; |
|
}; |
|
|
|
// Processes a histogram consructed from two arrays, 'a' and 'b'. |
|
// This function is used generically by the below set operation |
|
// methods, a.k.a, 'evaluators', to return some subset of |
|
// a set union, based on frequencies in the histogram. |
|
function process(a, b, evaluator, processCallback) { |
|
async.waterfall([ |
|
function aHistogram(callback){ |
|
// Create a histogram of 'a'. |
|
var hist = Object.create(null), ukey; |
|
async.each(a, |
|
function(value,cb) { |
|
ukey = uid.call(value); |
|
if(!hist[ukey]) { |
|
hist[ukey] = { value: value, freq: 1 }; |
|
} |
|
cb();}, |
|
function(err){ |
|
if(err) return callback(err); |
|
callback(null,hist); |
|
}); |
|
}, function bHistogram(hist,callback) { |
|
// Merge 'b' into the histogram. |
|
var ukey; |
|
async.each(b, |
|
function(value,cb) { |
|
ukey = uid.call(value); |
|
if (hist[ukey]) { |
|
if (hist[ukey].freq === 1) |
|
hist[ukey].freq = 3; |
|
} else { |
|
hist[ukey] = { value: value, freq: 2 }; |
|
} |
|
cb();}, |
|
function(err) { |
|
if(err) return callback(err); |
|
callback(null,hist); |
|
}); |
|
}, function evaluate(hist, callback){ |
|
// Call the given evaluator. |
|
if (typeof evaluator == 'function') { |
|
var out = []; |
|
async.forEachOf(hist,function(item,k,cb) { |
|
if (evaluator(item.freq)) out.push(item.value); |
|
cb(); |
|
},function(err){ |
|
if(err) return callback(err); |
|
callback(out); |
|
}); |
|
} else { |
|
return callback(hist); |
|
} |
|
} |
|
],processCallback); |
|
}; |
|
|
|
// Join two sets together. |
|
// Set.union([1, 2, 2], [2, 3]) => [1, 2, 3] |
|
so.union = function(a, b, cb) { |
|
process(a, b, function(freq) { |
|
return true; |
|
}, cb); |
|
}; |
|
|
|
// Return items common to both sets. |
|
// Set.intersection([1, 1, 2], [2, 2, 3]) => [2] |
|
so.intersection = function(a, b, cb) { |
|
return process(a, b, function(freq) { |
|
return freq === 3; |
|
}, cb); |
|
}; |
|
|
|
// Symmetric difference. Items from either set that |
|
// are not in both sets. |
|
// Set.difference([1, 1, 2], [2, 3, 3]) => [1, 3] |
|
so.difference = function(a, b, cb) { |
|
return process(a, b, function(freq) { |
|
return freq < 3; |
|
}, cb); |
|
}; |
|
|
|
// Relative complement. Items from 'a' which are |
|
// not also in 'b'. |
|
// Set.complement([1, 2, 2], [2, 2, 3]) => [3] |
|
so.complement = function(a, b, cb) { |
|
return process(a, b, function(freq) { |
|
return freq === 1; |
|
}, cb); |
|
}; |
|
|
|
// Returns true if both sets are equivalent, false otherwise. |
|
// Set.equals([1, 1, 2], [1, 2, 2]) => true |
|
// Set.equals([1, 1, 2], [1, 2, 3]) => false |
|
so.equals = function(a, b, cb) { |
|
process(a, b,null, function(err, hist){ |
|
if(err) return cb(err); |
|
async.detect(Object.keys(hist),function(key, cb){ |
|
cb(hist[key].freq != 3); |
|
},function(result){ |
|
cb(typeof result == 'undefined'); |
|
}); |
|
}); |
|
}; |
|
|
|
//export mechanism taken from async.js |
|
|
|
// Node.js |
|
if (typeof module === 'object' && module.exports) { |
|
module.exports = so; |
|
} |
|
// AMD / RequireJS |
|
else if (typeof define === 'function' && define.amd) { |
|
define([], function () { |
|
return so; |
|
}); |
|
} |
|
// included directly via <script> tag |
|
else { |
|
root.setOps = so; |
|
} |
|
|
|
})(); |