-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
235 lines (211 loc) · 7.78 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// ****************************************************************************************************
// Init dependencies and constructor
// ****************************************************************************************************
(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define(["big-integer"], factory);
} else if (typeof exports === "object") {
// Node, CommonJS-like
module.exports = factory(require("big-integer"));
} else {
// Browser globals (root is window)
root.Facetor = factory(root.bigInt);
}
})(this, function(bigInt) {
return function(options) {
options = options || {};
options.separator = options.separator || ".";
// ****************************************************************************************************
// init constructor scope variables
// ****************************************************************************************************
var index = {};
var result = {};
// ****************************************************************************************************
// shared functions
// ****************************************************************************************************
// deep clone an object
function deepClone(obj) {
return obj ? JSON.parse(JSON.stringify(obj)) : null;
}
// map object - replica of underscore's mapObject, operates like array.map
function mapObject(obj, iterator, initialValue) {
var rslt = deepClone(obj);
Object.keys(rslt).forEach(function(key) {
var value = rslt[key];
rslt[key] = iterator(value, key, rslt);
});
return rslt;
}
// reduce object - similar to underscore's mapObject, but operates like array.reduce
function reduceObject(obj, iterator, initialValue) {
var rslt = deepClone(obj);
return Object.keys(rslt).reduce(function(accumulator, currentKey) {
var currentValue = rslt[currentKey];
return iterator(accumulator, currentValue, currentKey, rslt);
}, initialValue);
}
// ****************************************************************************************************
// build index
// ****************************************************************************************************
// build index base object based on facets
function getBaseData(rslt, params) {
params.facets.forEach(function(key) {
rslt[key] = {};
params.items.forEach(function(item) {
if (item[key]) {
var values = Array.isArray(item[key]) ? item[key] : [item[key]];
values.forEach(function(value) {
var proceed = !(
params.blacklist && params.blacklist.indexOf(value) > -1
);
if (proceed) {
rslt[key][value] = "";
}
});
}
});
});
return rslt;
}
// populate index by converting item positions to bitmap
function populateBaseData(rslt, params) {
return mapObject(rslt, function(category, categoryKey) {
return mapObject(category, function(bitmap, fieldKey) {
params.items.forEach(function(item) {
var itemValues = Array.isArray(item[categoryKey])
? item[categoryKey]
: [item[categoryKey]];
var itemBitmap = itemValues.indexOf(fieldKey) > -1 ? "1" : "0";
bitmap = itemBitmap + bitmap;
});
return bitmap;
});
});
}
// convert binary bitmap to base 10 string
function convertBaseData(rslt) {
return mapObject(rslt, function(category, categoryKey) {
return mapObject(category, function(bitmap, fieldKey) {
return bigInt(bitmap, 2).toString();
});
});
}
// build index obj based on item facet values
function buildIndexData(params) {
var rslt = {};
rslt = getBaseData(rslt, params);
rslt = populateBaseData(rslt, params);
rslt = convertBaseData(rslt);
return rslt;
}
// ****************************************************************************************************
// build results
// ****************************************************************************************************
// evaluate child bitmaps using operator
function evalBitmap(operator, obj, key, facets, length) {
var baseBitmap =
operator == "and"
? bigInt(1)
.shiftLeft(length)
.minus(1)
: bigInt(0);
return reduceObject(
obj,
function(rslt, bitmap, childKey) {
var path = key ? key + options.separator + childKey : childKey;
if (operator == "and") {
return bitmap != "0" ? rslt.and(bitmap) : rslt;
} else {
return facets.indexOf(path) > -1 ? rslt.or(bitmap) : rslt;
}
},
baseBitmap
);
}
// get count of current bitmap
function getCount(bitmap) {
var tempBitmap = bigInt(bitmap.toString());
var rslt = 0;
while (tempBitmap > 0) {
tempBitmap = tempBitmap.and(tempBitmap.minus(1));
rslt++;
}
return rslt;
}
// get full bitmap of index based on current facets
function getBitmap(index, facets) {
var length = index.items.length;
var categoryBitmaps = mapObject(index.data, function(
category,
categoryKey
) {
return evalBitmap("or", category, categoryKey, facets, length);
});
return evalBitmap("and", categoryBitmaps, null, facets, length);
}
// build result data
function buildResultData(index, params, baseBitmap) {
var baseCount = params.facets.length ? getCount(baseBitmap) : 0;
return mapObject(index.data, function(category, categoryKey) {
return mapObject(category, function(field, fieldKey) {
field = {};
var path = categoryKey + options.separator + fieldKey;
var bitmap = getBitmap(index, params.facets.concat([path]));
var count = getCount(bitmap);
if (params.attributes.indexOf("path") > -1) {
field.path = path;
}
if (params.attributes.indexOf("bitmap") > -1) {
field.bitmap = bitmap;
}
if (params.attributes.indexOf("count") > -1) {
field.count = count;
}
if (params.attributes.indexOf("increment") > -1) {
field.increment = count - baseCount;
}
if (params.attributes.indexOf("status") > -1) {
field.status = params.facets.indexOf(path) > -1;
}
return field;
});
});
}
// build result items
function buildResultItems(index, baseBitmap) {
return index.items.filter(function(elem, idx) {
return baseBitmap.and(bigInt(1).shiftLeft(idx)) > 0;
});
}
// ****************************************************************************************************
// expose functions
// ****************************************************************************************************
// expose - build index
this.buildIndex = function(params) {
index = {
data: buildIndexData(params),
items: deepClone(params.items)
};
};
// expose - build result
this.buildResult = function(params) {
var bitmap = getBitmap(index, params.facets);
result = {
data: buildResultData(index, params, bitmap),
items: buildResultItems(index, bitmap)
};
return result;
};
// expose - import index
this.importIndex = function(params) {
index = deepClone(params);
};
// expose - export index
this.exportIndex = function() {
return deepClone(index);
};
// return
return this;
};
});