UNPKG

json-tim

Version:

Functions for timsort with SQL-like json

161 lines (139 loc) 5.25 kB
export 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) */ export function timsort(json, sort, ids=undefined, transform={}) { let use, sortable, ignorable; use = (ids === undefined) ? Object.keys(json) : ids.concat(); [sortable, ignorable] = sortableRecords(json, use, sort.map(s=>s.field), transform) // no sort specified, return filtered in order that it was given if (!sort.length) return use // setting up the comparison functions fo rthe TimSort let comparisons = sort.map(({field, isAscending}) => { // each comparsion function takes the two record ids, extracts their values, // and then passes it to the simpleSort function return (id1, id2) => { const extractor = (field in transform) ? transform[field] : identity let a = extractor(id1, field, json[id1]) let b = extractor(id2, field, json[id2]) return simpleSort(a, b, isAscending) } }) // the actual TimSort let sorted = sortable.sort((a, b) => { // while there is no difference, keep trying comparisons let difference for (let 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) */ export function sortableRecords(json, ids, fields, transform) { let sortable = [], ignorable = []; ids.forEach(id => { let 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) */ export function isRecordSortable(json, id, fields, transform) { let anyMissing = fields.some(field => { let extractor = (field in transform) ? transform[field] : identity let 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 */ export function stringSort(a, b, isAscending) { const lang = 'en' const 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 */ export function numberSort(a, b, isAscending) { return isAscending ? a - b : b - a } export 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 */ export 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 } }