UNPKG

json-tim

Version:

Functions for timsort with SQL-like json

226 lines (196 loc) 7.72 kB
(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 }); })));