//@ts-nocheck /** * @class ArrayRepr * * Class for operating on indexed array representations of objects. * * For example, if we have a lot of objects with similar attributes, e.g.: * *
* [
* {start: 1, end: 2, strand: -1},
* {start: 5, end: 6, strand: 1},
* ...
* ]
*
*
* @description
* we can represent them more compactly (e.g., in JSON) something like this:
*
* * class = ["start", "end", "strand"] * [ * [1, 2, -1], * [5, 6, 1], * ... * ] ** * If we want to represent a few different kinds of objects in our big list, * we can have multiple "class" arrays, and tag each object to identify * which "class" array describes it. * * For example, if we have a lot of instances of a few types of objects, * like this: * *
* [
* {start: 1, end: 2, strand: 1, id: 1},
* {start: 5, end: 6, strand: 1, id: 2},
* ...
* {start: 10, end: 20, chunk: 1},
* {start: 30, end: 40, chunk: 2},
* ...
* ]
*
*
* We could use the first array position to indicate the "class" for the
* object, like this:
*
* * classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]] * [ * [0, 1, 2, 1, 1], * [0, 5, 6, 1, 2], * ... * [1, 10, 20, 1], * [1, 30, 40, 1] * ] ** * Also, if we occasionally want to add an ad-hoc attribute, we could just * stick an optional dictionary onto the end: * *
* classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
* [
* [0, 1, 2, 1, 1],
* [0, 5, 6, 1, 2, {foo: 1}]
* ]
*
*
* Given that individual objects are being represented by arrays, generic
* code needs some way to differentiate arrays that are meant to be objects
* from arrays that are actually meant to be arrays.
* So for each class, we include a dict with
* classes=[
* {'attributes': ['Start', 'End', 'Subfeatures'],
* 'proto': {'Chrom': 'chr1'},
* 'isArrayAttr': {Subfeatures: true}}
* ]
*
*
* That's what this class facilitates.
*/
class ArrayRepr {
constructor(classes) {
this.classes = classes
this.fields = []
for (let cl = 0; cl < classes.length; cl += 1) {
this.fields[cl] = {}
for (let f = 0; f < classes[cl].attributes.length; f += 1) {
this.fields[cl][classes[cl].attributes[f]] = f + 1
}
if (classes[cl].proto === undefined) {
classes[cl].proto = {}
}
if (classes[cl].isArrayAttr === undefined) {
classes[cl].isArrayAttr = {}
}
}
}
/**
* @private
*/
attrIndices(attr) {
return this.classes.map(
x =>
x.attributes.indexOf(attr) + 1 ||
x.attributes.indexOf(attr.toLowerCase()) + 1 ||
undefined,
)
}
get(obj, attr) {
if (attr in this.fields[obj[0]]) {
return obj[this.fields[obj[0]][attr]]
}
// try lowercase
const lcattr = attr.toLowerCase()
if (lcattr in this.fields[obj[0]]) {
return obj[this.fields[obj[0]][lcattr]]
}
const adhocIndex = this.classes[obj[0]].attributes.length + 1
if (adhocIndex >= obj.length || !(attr in obj[adhocIndex])) {
if (attr in this.classes[obj[0]].proto) {
return this.classes[obj[0]].proto[attr]
}
return undefined
}
return obj[adhocIndex][attr]
}
makeGetter(attr) {
return obj => {
return this.get(obj, attr)
}
}
makeFastGetter(attr) {
// can be used only if attr is guaranteed to be in
// the "classes" array for this object
const indices = this.attrIndices(attr)
return function get(obj) {
if (indices[obj[0]] !== undefined) {
return obj[indices[obj[0]]]
}
return undefined
}
}
/**
* Returns fast pre-compiled getter and setter functions for use with
* Arrays that use this representation.
* When the returned get and set functions are
* added as methods to an Array that contains data in this
* representation, they provide fast access by name to the data.
*
* @returns {Object} { get: function() {...}, set: function(val) {...} }
*
* @example
* var accessors = attrs.accessors();
* var feature = get_feature_from_someplace();
* feature.get = accessors.get;
* // print out the feature start and end
* console.log( feature.get('start') + ',' + feature.get('end') );
*/
accessors() {
if (!this._accessors) {
this._accessors = this._makeAccessors()
}
return this._accessors
}
/**
* @private
*/
_makeAccessors() {
const indices = {}
const accessors = {
get(field) {
const f = this.get.field_accessors[field.toLowerCase()]
if (f) {
return f.call(this)
}
return undefined
},
set(field, val) {
const f = this.set.field_accessors[field]
if (f) {
return f.call(this, val)
}
return undefined
},
tags() {
return tags[this[0]] || []
},
}
accessors.get.field_accessors = {}
accessors.set.field_accessors = {}
// make a data structure as: { attr_name: [offset,offset,offset], }
// that will be convenient for finding the location of the attr
// for a given class like: indexForAttr{attrname}[classnum]
this.classes.forEach((cdef, classnum) => {
;(cdef.attributes || []).forEach((attrname, offset) => {
indices[attrname] = indices[attrname] || []
indices[attrname][classnum] = offset + 1
attrname = attrname.toLowerCase()
indices[attrname] = indices[attrname] || []
indices[attrname][classnum] = offset + 1
})
})
// lowercase all the class attributes
const tags = this.classes.map(c => c.attributes)
// GFF3 standard column fields that are always numeric
const numericFields = new Set(['start', 'end', 'strand', 'phase', 'score'])
// use that to make precalculated get and set accessors for each field
Object.keys(indices).forEach(attrname => {
const attrIndices = indices[attrname]
const isNumeric = numericFields.has(attrname)
// get
accessors.get.field_accessors[attrname] = !attrIndices
? function get() {
return undefined
}
: function get() {
const val = this[attrIndices[this[0]]]
if (isNumeric && typeof val === 'string') {
return +val
}
return val
}
})
return accessors
}
}
export default ArrayRepr
/*
Copyright (c) 2007-2010 The Evolutionary Software Foundation
Created by Mitchell Skinner