json-tim
Version:
Functions for timsort with SQL-like json
226 lines (196 loc) • 7.72 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global['json-tim'] = global['json-tim'] || {})));
}(this, (function (exports) { 'use strict';
function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
function identity(id, field, record) {
return record[field];
}
/**
* Peforms timsort on an SQL-like object
* @params {object} json - an SQL-like object
* @params {array} sort - a list of sort specifications
* @params {array} ids - a subset of record ids to sort, if not specified, applied to all
* @params {object} transform - an object of {field: function} pairs which modified the value to use when sorting
* functions of in transform should be defined to take the arguments (id, record, field)
*/
function timsort(json, sort) {
var ids = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
var transform = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var use, sortable, ignorable;
use = ids === undefined ? Object.keys(json) : ids.concat();
var _sortableRecords = sortableRecords(json, use, sort.map(function (s) {
return s.field;
}), transform);
var _sortableRecords2 = _slicedToArray(_sortableRecords, 2);
sortable = _sortableRecords2[0];
ignorable = _sortableRecords2[1];
// no sort specified, return filtered in order that it was given
if (!sort.length) return use; // setting up the comparison functions fo rthe TimSort
var comparisons = sort.map(function (_ref) {
var field = _ref.field,
isAscending = _ref.isAscending;
// each comparsion function takes the two record ids, extracts their values,
// and then passes it to the simpleSort function
return function (id1, id2) {
var extractor = field in transform ? transform[field] : identity;
var a = extractor(id1, field, json[id1]);
var b = extractor(id2, field, json[id2]);
return simpleSort(a, b, isAscending);
};
}); // the actual TimSort
var sorted = sortable.sort(function (a, b) {
// while there is no difference, keep trying comparisons
var difference;
for (var i = 0; i < comparisons.length; i++) {
difference = comparisons[i](a, b);
if (!difference) continue;else break;
}
return difference;
}); // return sorted and tack on the unsortable
return sorted.concat(ignorable);
}
/**
* Determines which records can be sorted (has sortable values)
* @params {object} json - an SQL-like object
* @params {array} ids - a subset of record ids to sort, if not specified, applied to all
* @params {array} fields - a list of fields which occur in each record
* @params {object} transform - an object of {field: function} pairs which modified the value to use when sorting
* functions of in transform should be defined to take the arguments (id, record, field)
*/
function sortableRecords(json, ids, fields, transform) {
var sortable = [],
ignorable = [];
ids.forEach(function (id) {
var isSortable = isRecordSortable(json, id, fields, transform);
if (isSortable) {
// if all fields have a defined value, this record is sortable
sortable.push(id);
} else {
// at least one field is missing a value, not sortable
ignorable.push(id);
}
});
return [sortable, ignorable];
}
/**
* Determines which if given record can be sorted on specified fields
* @params {object} json - an SQL-like object
* @params {array} id - the record id under consideration
* @params {array} fields - the fields on which to sort
* @params {object} transform - an object of {field: function} pairs which modified the value to use when sorting
* functions of in transform should be defined to take the arguments (id, record, field)
*/
function isRecordSortable(json, id, fields, transform) {
var anyMissing = fields.some(function (field) {
var extractor = field in transform ? transform[field] : identity;
var v = extractor(id, field, json[id]);
return typeof v === 'undefined' || v == null;
});
return !anyMissing;
}
/**
* Comparison function for string values.
* Use of the method localeCompare with the following options specified:
* - lang='en'
* - sensitivity='base'
* - ignorePunctuation=true
* @params a - first value
* @params b - second value
* @params {boolean} isAscending - whether or not to sort ascending or descending
*/
function stringSort(a, b, isAscending) {
var lang = 'en';
var opts = {
sensitivity: 'base',
ignorePunctuation: true
};
return isAscending ? a.localeCompare(b, lang, opts) : b.localeCompare(a, lang, opts);
}
/**
* Comparison function for numerical values.
* By default JavaScript does _not_ sort numbers numerically.
* @params a - first value
* @params b - second value
* @params {boolean} isAscending - whether or not to sort ascending or descending
*/
function numberSort(a, b, isAscending) {
return isAscending ? a - b : b - a;
}
function dateSort(a, b, isAscending) {
return isAscending ? a - b : b - a;
}
/**
* A simple, dynamic sorting function. If both values are are string, compares
* strings; if both numeric, compares magnitude, else returns none.
* @params a - first value
* @params b - second value
* @params {boolean} isAscending - whether or not to sort ascending or descending
*/
function simpleSort(a, b, isAscending) {
if (typeof a == 'string' && typeof b == 'string') {
return stringSort(a, b, isAscending);
} else if (typeof a == 'number' && typeof b == 'number') {
return numberSort(a, b, isAscending);
} else if (_typeof(a) === 'object' && _typeof(b) === 'object' && !isNaN(Date.parse(a)) && !isNaN(Date.parse(b))) {
return dateSort(a, b, isAscending);
} else {
// return numberSort(a, b, isAscending)
return; //-Infinity
}
}
var jsontim = {
identity: identity,
timsort: timsort,
sortableRecords: sortableRecords,
isRecordSortable: isRecordSortable,
stringSort: stringSort,
numberSort: numberSort,
simpleSort: simpleSort
};
exports.default = jsontim;
Object.defineProperty(exports, '__esModule', { value: true });
})));