Skip to content

Commit

Permalink
Merge branch 'pivotGroup'
Browse files Browse the repository at this point in the history
Recompiled crossfilter to include pivotGroup functionality.

Conflicts:
	crossfilter.min.js
  • Loading branch information
Trakkasure committed Jan 16, 2013
2 parents 819dfbf + 1d767ab commit de4bae8
Show file tree
Hide file tree
Showing 6 changed files with 677 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
node_modules
.*.swp
221 changes: 221 additions & 0 deletions crossfilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,10 +515,58 @@ function crossfilter_reduceSubtract(f) {
}
exports.crossfilter = crossfilter;


// Fowler/Noll/Vo hashing. From https://github.com/jasondavies/bloomfilter.js, modified for arrays of integers
function fnv_1a(v) {
var n = v.length,
a = 2166136261,
c,
d,
i = -1;
while (++i < n) {
c = v[i];
if (d = c & 0xff000000) {
a ^= d >> 24;
a += (a << 1) + (a << 4) + (a << 7) + (a << 8) + (a << 24);
}
if (d = c & 0xff0000) {
a ^= d >> 16;
a += (a << 1) + (a << 4) + (a << 7) + (a << 8) + (a << 24);
}
if (d = c & 0xff00) {
a ^= d >> 8;
a += (a << 1) + (a << 4) + (a << 7) + (a << 8) + (a << 24);
}
a ^= c & 0xff;
a += (a << 1) + (a << 4) + (a << 7) + (a << 8) + (a << 24);
}
// From http://home.comcast.net/~bretm/hash/6.html
a += a << 13;
a ^= a >> 7;
a += a << 3;
a ^= a >> 17;
a += a << 5;
return a & 0xffffffff;
}

// One additional iteration of FNV, given a hash.
function fnv_1a_b(a) {
a += (a << 1) + (a << 4) + (a << 7) + (a << 8) + (a << 24);
a += a << 13;
a ^= a >> 7;
a += a << 3;
a ^= a >> 17;
a += a << 5;
return a & 0xffffffff;
}



function crossfilter() {
var crossfilter = {
add: add,
dimension: dimension,
pivotGroup: pivotGroup,
groupAll: groupAll,
size: size
};
Expand Down Expand Up @@ -805,6 +853,7 @@ function crossfilter() {
// Adds a new group to this dimension, using the specified key function.
function group(key) {
var group = {
_groupIndex: _groupIndex,
top: top,
all: all,
reduce: reduce,
Expand Down Expand Up @@ -1046,6 +1095,11 @@ function crossfilter() {
return groups;
}

function _groupIndex() {
if (resetNeeded) reset(), resetNeeded = false;
return groupIndex;
}

// Returns a new array containing the top K group values, in reduce order.
function top(k) {
var top = select(all(), 0, groups.length, k);
Expand Down Expand Up @@ -1248,6 +1302,173 @@ function crossfilter() {
return n;
}

function pivotGroup(groups) {
var pivotGroup = {
all: all,
size: size,
reduce: reduce,
reduceCount: reduceCount,
reduceSum: reduceSum
}

var pivotGroups, // array of {key, value}
pivotGroupIndex, // object id ↦ group id
k = 0, // cardinality
groupsLength = groups.length,
reduceAdd,
reduceRemove,
reduceInitial,
resetNeeded = true

filterListeners.push(update);

function pivotKeyEqual(lhs, rhs) {
var i, rslt = true
for(i=0; rslt && i<groupsLength; i++)
rslt = lhs[i] == rhs[i]
return rslt
}

function pivotKeySort(lhs, rhs) {
var i, rslt = 0
for(i=0; !rslt && i<groupsLength; i++)
rslt = lhs[i] < rhs[i] ? -1 : lhs[i] > rhs[i] ? 1 : 0
return rslt
}

function reset() {
pivotGroups = []
pivotGroupIndex = []

function collectPivotGroupsAndIndexes() {
var i, j, hashProbe0, hashProbe, hashStep, key,
groupIndexes = [],
bucketsLength = Math.max(Math.pow(2, Math.ceil(Math.log(n*1.5)/Math.log(2))), 256), // worst case 66% fill if every record is in a different bucket, but usually fill will be much lower
bucketsMask = bucketsLength-1,
buckets = new Array(bucketsLength)

for(i=0; i<groupsLength; i++) groupIndexes.push(groups[i]._groupIndex())

for(i=0; i<n; i++) {
key = []
for(j=0; j<groupsLength; j++) key.push(groupIndexes[j] ? groupIndexes[j][i] : 0)
hashProbe0 = -1
hashProbe = fnv_1a(key) & bucketsMask
hashStep = fnv_1a_b(hashProbe)
hashProbe = hashProbe & bucketsMask
while(key && buckets[hashProbe]) {
if (pivotKeyEqual(pivotGroups[buckets[hashProbe]-1], key)) {
pivotGroupIndex.push(buckets[hashProbe]-1)
key = null
} else {
if (hashProbe0 == hashProbe) hashStep = 1 // prevent any chance of infinite loop due to hashStep not being relatively prime to bucketsLength
if (hashProbe0 == -1) hashProbe0 = hashProbe
hashProbe = (hashProbe + hashStep) & bucketsMask
}
}
if (key) {
buckets[hashProbe] = pivotGroups.length+1
pivotGroupIndex.push(pivotGroups.length)
pivotGroups.push(key)
}
}
k = pivotGroups.length
}

function sortPivotGroupsAndRemapIndexes() {
var i, pivotGroupIndexRemap = []
for(i=0; i<k; i++) pivotGroups[i].push(i)
pivotGroups.sort(pivotKeySort)
for(i=0; i<k; i++) pivotGroupIndexRemap[pivotGroups[i][groupsLength]] = i
for(i=0; i<n; i++) pivotGroupIndex[i] = pivotGroupIndexRemap[pivotGroupIndex[i]]
}

function constructPivotGroupObjects() {
var i, g, key, groupKeys = []
for(i=0; i<groupsLength; i++) groupKeys.push(groups[i].all())
for(i=0; i<k; i++) {
key = []
for(j=0; j<groupsLength; j++) key.push(groupKeys[j][pivotGroups[i][j]].key)
pivotGroups[i] = {key:key}
}
}

function resetPivotGroups() {
var i, g
for (i = 0; i < k; ++i) {
pivotGroups[i].value = reduceInitial()
}

// Add any selected records.
for (i = 0; i < n; ++i) {
if (!filters[i]) {
g = pivotGroups[pivotGroupIndex[i]]
g.value = reduceAdd(g.value, data[i]);
}
}
}

collectPivotGroupsAndIndexes()
sortPivotGroupsAndRemapIndexes()
constructPivotGroupObjects()
resetPivotGroups()
}

function update(ignored, added, removed) {
if (resetNeeded) return

var i, k, n, g

// Add the added values.
for (i = 0, n = added.length; i < n; ++i) {
if (!filters[k = added[i]]) {
g = pivotGroups[pivotGroupIndex[k]];
g.value = reduceAdd(g.value, data[k]);
}
}

// Remove the removed values.
for (i = 0, n = removed.length; i < n; ++i) {
if (filters[k = removed[i]]) {
g = pivotGroups[pivotGroupIndex[k]];
g.value = reduceRemove(g.value, data[k]);
}
}
}

function all() {
if (resetNeeded) reset(), resetNeeded = false;
return pivotGroups
}

function size() {
if (resetNeeded) reset(), resetNeeded = false;
return k
}

// Sets the reduce behavior for this group to use the specified functions.
// This method lazily recomputes the reduce values, waiting until needed.
function reduce(add, remove, initial) {
reduceAdd = add
reduceRemove = remove
reduceInitial = initial
resetNeeded = true
return pivotGroup
}

// A convenience method for reducing by count.
function reduceCount() {
return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero)
}

// A convenience method for reducing by sum(value).
function reduceSum(value) {
return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero)
}

return reduceCount()
}

return arguments.length
? add(arguments[0])
: crossfilter;
Expand Down
Loading

0 comments on commit de4bae8

Please sign in to comment.