Skip to content

Commit

Permalink
Build results index with attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Tony Ko committed Apr 10, 2018
1 parent 6d5a25e commit e654c09
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 90 deletions.
2 changes: 1 addition & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ catalogFacets.import(index);

// get results
var results = catalogFacets.results({
facets: ["title.rural", "title.misc", "category.locks"],
facets: ["title.rural", "title.misc"],
operators: ["AND", "OR"],
attributes: ["count"]
});
Expand Down
192 changes: 103 additions & 89 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,50 @@ function Constructor() {
// ****************************************************************************************************

// shared - deep clone an object
function deepClone(obj){
function deepClone(obj) {
return obj ? JSON.parse(JSON.stringify(obj)) : null;
}

// shared - Set an object's deep nested property based on "." delimited string path
function setNode(obj, path, val) {
path = path.split('.');
for (i = 0; i < path.length - 1; i++){
if(_.keys(obj).indexOf(path[i]) > -1){
for (i = 0; i < path.length - 1; i++) {
if (_.keys(obj).indexOf(path[i]) > -1) {
obj = obj[path[i]];
}
}
if(_.keys(obj).indexOf(path[i]) > -1){
if (_.keys(obj).indexOf(path[i]) > -1) {
obj[path[i]] = val;
}
}

// shared - Get an object's deep nested property based on "." delimited string path
function getNode(obj, path){
for (var i=0, path=path.split('.'), len=path.length; i<len; i++){
if(_.keys(obj).indexOf(path[i]) > -1){
function getNode(obj, path) {
for (var i = 0, path = path.split('.'), len = path.length; i < len; i++) {
if (_.keys(obj).indexOf(path[i]) > -1) {
obj = obj[path[i]];
}
};
return obj;
};

// shared - reduce object using its keys - very similar to reduce for arrays - syntax:
// obj.reduceObj(function(accumulator, currentValue, currentKey, self){
// return accumulator
// }, initialValue)
Object.prototype.reduceObj = function(callback, initialValue){
var self = this;
return _.keys(self).reduce(function(accumulator, currentKey){
var currentValue = self[currentKey];
return callback(accumulator, currentValue, currentKey, self)
// shared - reduce object using its keys - very similar to reduce for arrays
// syntax:
// _.reduceObject(obj, function(accumulator, currentValue, currentKey, self){
// return accumulator
// }, initialValue)
_.reduceObject = function (obj, callback, initialValue) {
return _.keys(obj).reduce(function (accumulator, currentKey) {
var currentValue = obj[currentKey];
return callback(accumulator, currentValue, currentKey, obj)
}, initialValue)
}


// ****************************************************************************************************
// init constructor scope variables
// ****************************************************************************************************

var store = {
index: {},
items: []
Expand All @@ -69,72 +69,63 @@ function Constructor() {
// ****************************************************************************************************
// build index
// ****************************************************************************************************

// build index based on item facet values
function buildIndex(index, facets, items){
var rslt = index
facets.forEach(function(facet){
rslt[facet] = {};
items.forEach(function(item){
if(item[facet]){
var values = Array.isArray(item[facet]) ? item[facet] : [item[facet]];
values.forEach(function(value) {
rslt[facet][value] = {_bitmap: ''};
function buildStoreIndex(store, params) {
var rslt = store.index
params.facets.forEach(function (facetKey) {
rslt[facetKey] = {};
params.items.forEach(function (item) {
if (item[facetKey]) {
var values = Array.isArray(item[facetKey]) ? item[facetKey] : [item[facetKey]];
values.forEach(function (value) {
rslt[facetKey][value] = { _bitmap: '' };
});
}
})
});
return rslt;
}

// populate index by converting item positions to bitmap
function populateIndexValues(index, items){
var rslt = index;
items.forEach(function(item){
_.keys(index).forEach(function(facet){
_.keys(index[facet]).forEach(function(value){
var values = Array.isArray(item[facet]) ? item[facet] : [item[facet]];
var bitmap = values.indexOf(value) > -1 ? '1' : '0';
rslt[facet][value]._bitmap = bitmap + rslt[facet][value]._bitmap;
// populate index by converting item positions to bitmap
rslt = _.mapObject(rslt, function (facet, facetKey) {
return _.mapObject(facet, function (value, valueKey) {
params.items.forEach(function (item) {
var values = Array.isArray(item[facetKey]) ? item[facetKey] : [item[facetKey]];
var exist = values.indexOf(valueKey) > -1 ? '1' : '0';
value._bitmap = exist + value._bitmap;
})
})
})
return rslt;
}

// convert binary bitmap to base 10 string
function convertIndexValues(index){
var rslt = index;
_.keys(index).forEach(function(facet){
_.keys(index[facet]).forEach(function(value){
var bitmap = bigInt(rslt[facet][value]._bitmap, 2).toString();
rslt[facet][value]._bitmap = bitmap
})
})
return value
});
});
// convert binary bitmap to base 10 string
rslt = _.mapObject(rslt, function (facet, facetKey) {
return _.mapObject(facet, function (value, valueKey) {
var bitmap = bigInt(value._bitmap, 2).toString();
value._bitmap = bitmap
return value
});
});
return rslt;
}

// expose - build function
this.build = function(params){
store.index = buildIndex(store.index, params.facets, params.items);
store.index = populateIndexValues(store.index, params.items);
store.index = convertIndexValues(store.index);
store.items = params.items;
this.build = function (params) {
store.index = buildStoreIndex(store, params);
store.items = deepClone(params.items);
return store;
}


// ****************************************************************************************************
// import / export
// ****************************************************************************************************

// expose - export function
this.export = function(){
console.log(JSON.stringify(store, null, "\t"));
this.export = function () {
// console.log(JSON.stringify(store, null, "\t"));
return deepClone(store);
}

// expose - import function
this.import = function(params){
this.import = function (params) {
store = deepClone(params);
}

Expand All @@ -144,16 +135,16 @@ function Constructor() {
// ****************************************************************************************************

// get config for bitmap node
function getBitmapCfg(operators, level, length, facets){
function getBitmapCfg(operators, level, length, facets) {
var operator = ["AND", "OR"].indexOf(operators[level]) > -1 ? operators[level] : "AND";
var cfg = {
"AND": {
initial: bigInt(1).shiftLeft(length).minus(1),
operation: function(operand1, operand2){ return operand1.and(operand2) }
operation: function (operand1, operand2) { return operand1.and(operand2) }
},
"OR": {
initial: bigInt(0),
operation: function(operand1, operand2){ return operand1.or(operand2) }
operation: function (operand1, operand2) { return operand1.or(operand2) }
}
}
return {
Expand All @@ -164,42 +155,65 @@ function Constructor() {
}

// get bitmap
function getBitmap(index, path, level, length, operators, facets){
var bitmapCfg = getBitmapCfg(operators, level, length);
var rslt = index.reduceObj(function(accumulator, val, key){
// set child variables
var childPath = path ? path+"."+key : key;
var childLevel = level + 1;
// if child is facet, use bitmap as operand, if not, recurse and retrieve total bitmap of children;
var operand = val.hasOwnProperty("_bitmap") ? bigInt(val._bitmap) : getBitmap(val, childPath, childLevel, length, operators, facets);
// operate if current path is related to any facet
var operate = _.findIndex(facets, function(facet){
return childPath.indexOf(facet) == 0 || facet.indexOf(childPath) == 0;
function getBitmap(index, params, path) {
var level = ((path.match(/\./g) || []).length + 1);
var bitmapCfg = getBitmapCfg(params.operators, level, store.items.length);
var rslt = _.reduceObject(index, function (accumulator, node, nodeKey) {
// Recursively deep reduce all of node's children. On each level, operate on bitmap if nodePath is related to any item in facets param.
var nodePath = path ? path + "." + nodeKey : nodeKey;
var bitmap = node.hasOwnProperty("_bitmap") ? bigInt(node._bitmap) : getBitmap(node, params, nodePath);
var related = _.findIndex(params.facets, function (facet) {
return nodePath.indexOf(facet) == 0 || facet.indexOf(nodePath) == 0;
}) > -1;
// return result
return operate ? bitmapCfg.operation(accumulator, operand) : accumulator;
return related ? bitmapCfg.operation(accumulator, bitmap) : accumulator;
}, bitmapCfg.initial)
// avoid empty OR results)
if(bitmapCfg.operator == "OR"){
rslt = rslt.value ? rslt : bigInt(1).shiftLeft(length).minus(1);
// avoid empty OR results
if (bitmapCfg.operator == "OR") {
rslt = rslt.value ? rslt : bigInt(1).shiftLeft(store.items.length).minus(1);
}
return rslt;
}

// build items based on bitmap values
function buildResultsItems(store, params) {
var bitmap = getBitmap(store.index, params, "")
var rslt = store.items.filter(function (elem, idx) {
return bitmap.and(bigInt(1).shiftLeft(idx)) > 0;
})
return rslt;
}

function buildItems(){

// build bitmap count
function getCount(bitmap){
var rslt = 0;
while (bitmap > 0) {
// Count all the 1s in curBitmap, which is the count of matched products
bitmap = bitmap.and(bitmap.minus(1));
rslt++;
}
return rslt;
}

function populateIndexAttr(index){
var rslt = index;
// populate index based on store.index with attributes
function populateResultsIndex(index, store, params, path) {
return _.mapObject(index, function (node, nodeKey) {
// Recursively deep map all of node's children. On each level, populate attributes if params.attributes asks for it.
var nodePath = path ? path + "." + nodeKey : nodeKey;
var tempParams = deepClone(params);
tempParams.facets = params.facets.concat([nodePath])
node = node.hasOwnProperty("_bitmap") ? node : populateResultsIndex(node, store, params, nodePath);
if (params.attributes.indexOf("count") > -1){
var bitmap = getBitmap(store.index, tempParams, "")
node._count = getCount(bitmap);
}
return node;
})
return rslt;
}

this.results = function(params){
getBitmap(store.index, "", 0, store.items.length, params.operators, params.facets)
// results.items = buildItems();
// results.index = populateIndexAttr(store.index);
this.results = function (params) {
results.items = buildResultsItems(store, params);
results.index = populateResultsIndex(store.index, store, params, "")
return results;
}

Expand Down

0 comments on commit e654c09

Please sign in to comment.