UNPKG

7.75 kBJavaScriptView Raw
1'use strict'
2
3const clone = require('../../utils/object').clone
4const isInteger = require('../../utils/number').isInteger
5
6function factory (type) {
7 /**
8 * Create an index. An Index can store ranges and sets for multiple dimensions.
9 * Matrix.get, Matrix.set, and math.subset accept an Index as input.
10 *
11 * Usage:
12 * const index = new Index(range1, range2, matrix1, array1, ...)
13 *
14 * Where each parameter can be any of:
15 * A number
16 * A string (containing a name of an object property)
17 * An instance of Range
18 * An Array with the Set values
19 * A Matrix with the Set values
20 *
21 * The parameters start, end, and step must be integer numbers.
22 *
23 * @class Index
24 * @Constructor Index
25 * @param {...*} ranges
26 */
27 function Index (ranges) {
28 if (!(this instanceof Index)) {
29 throw new SyntaxError('Constructor must be called with the new operator')
30 }
31
32 this._dimensions = []
33 this._isScalar = true
34
35 for (let i = 0, ii = arguments.length; i < ii; i++) {
36 const arg = arguments[i]
37
38 if (type.isRange(arg)) {
39 this._dimensions.push(arg)
40 this._isScalar = false
41 } else if (Array.isArray(arg) || type.isMatrix(arg)) {
42 // create matrix
43 const m = _createImmutableMatrix(arg.valueOf())
44 this._dimensions.push(m)
45 // size
46 const size = m.size()
47 // scalar
48 if (size.length !== 1 || size[0] !== 1) {
49 this._isScalar = false
50 }
51 } else if (typeof arg === 'number') {
52 this._dimensions.push(_createImmutableMatrix([arg]))
53 } else if (typeof arg === 'string') {
54 // object property (arguments.count should be 1)
55 this._dimensions.push(arg)
56 } else {
57 throw new TypeError('Dimension must be an Array, Matrix, number, string, or Range')
58 }
59 // TODO: implement support for wildcard '*'
60 }
61 }
62
63 /**
64 * Attach type information
65 */
66 Index.prototype.type = 'Index'
67 Index.prototype.isIndex = true
68
69 function _createImmutableMatrix (arg) {
70 // loop array elements
71 for (let i = 0, l = arg.length; i < l; i++) {
72 if (typeof arg[i] !== 'number' || !isInteger(arg[i])) {
73 throw new TypeError('Index parameters must be positive integer numbers')
74 }
75 }
76 // create matrix
77 return new type.ImmutableDenseMatrix(arg)
78 }
79
80 /**
81 * Create a clone of the index
82 * @memberof Index
83 * @return {Index} clone
84 */
85 Index.prototype.clone = function () {
86 const index = new Index()
87 index._dimensions = clone(this._dimensions)
88 index._isScalar = this._isScalar
89 return index
90 }
91
92 /**
93 * Create an index from an array with ranges/numbers
94 * @memberof Index
95 * @param {Array.<Array | number>} ranges
96 * @return {Index} index
97 * @private
98 */
99 Index.create = function (ranges) {
100 const index = new Index()
101 Index.apply(index, ranges)
102 return index
103 }
104
105 /**
106 * Retrieve the size of the index, the number of elements for each dimension.
107 * @memberof Index
108 * @returns {number[]} size
109 */
110 Index.prototype.size = function () {
111 const size = []
112
113 for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
114 const d = this._dimensions[i]
115 size[i] = (typeof d === 'string') ? 1 : d.size()[0]
116 }
117
118 return size
119 }
120
121 /**
122 * Get the maximum value for each of the indexes ranges.
123 * @memberof Index
124 * @returns {number[]} max
125 */
126 Index.prototype.max = function () {
127 const values = []
128
129 for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
130 const range = this._dimensions[i]
131 values[i] = (typeof range === 'string') ? range : range.max()
132 }
133
134 return values
135 }
136
137 /**
138 * Get the minimum value for each of the indexes ranges.
139 * @memberof Index
140 * @returns {number[]} min
141 */
142 Index.prototype.min = function () {
143 const values = []
144
145 for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
146 const range = this._dimensions[i]
147 values[i] = (typeof range === 'string') ? range : range.min()
148 }
149
150 return values
151 }
152
153 /**
154 * Loop over each of the ranges of the index
155 * @memberof Index
156 * @param {Function} callback Called for each range with a Range as first
157 * argument, the dimension as second, and the
158 * index object as third.
159 */
160 Index.prototype.forEach = function (callback) {
161 for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
162 callback(this._dimensions[i], i, this)
163 }
164 }
165
166 /**
167 * Retrieve the dimension for the given index
168 * @memberof Index
169 * @param {Number} dim Number of the dimension
170 * @returns {Range | null} range
171 */
172 Index.prototype.dimension = function (dim) {
173 return this._dimensions[dim] || null
174 }
175
176 /**
177 * Test whether this index contains an object property
178 * @returns {boolean} Returns true if the index is an object property
179 */
180 Index.prototype.isObjectProperty = function () {
181 return this._dimensions.length === 1 && typeof this._dimensions[0] === 'string'
182 }
183
184 /**
185 * Returns the object property name when the Index holds a single object property,
186 * else returns null
187 * @returns {string | null}
188 */
189 Index.prototype.getObjectProperty = function () {
190 return this.isObjectProperty() ? this._dimensions[0] : null
191 }
192
193 /**
194 * Test whether this index contains only a single value.
195 *
196 * This is the case when the index is created with only scalar values as ranges,
197 * not for ranges resolving into a single value.
198 * @memberof Index
199 * @return {boolean} isScalar
200 */
201 Index.prototype.isScalar = function () {
202 return this._isScalar
203 }
204
205 /**
206 * Expand the Index into an array.
207 * For example new Index([0,3], [2,7]) returns [[0,1,2], [2,3,4,5,6]]
208 * @memberof Index
209 * @returns {Array} array
210 */
211 Index.prototype.toArray = function () {
212 const array = []
213 for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
214 const dimension = this._dimensions[i]
215 array.push((typeof dimension === 'string') ? dimension : dimension.toArray())
216 }
217 return array
218 }
219
220 /**
221 * Get the primitive value of the Index, a two dimensional array.
222 * Equivalent to Index.toArray().
223 * @memberof Index
224 * @returns {Array} array
225 */
226 Index.prototype.valueOf = Index.prototype.toArray
227
228 /**
229 * Get the string representation of the index, for example '[2:6]' or '[0:2:10, 4:7, [1,2,3]]'
230 * @memberof Index
231 * @returns {String} str
232 */
233 Index.prototype.toString = function () {
234 const strings = []
235
236 for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
237 const dimension = this._dimensions[i]
238 if (typeof dimension === 'string') {
239 strings.push(JSON.stringify(dimension))
240 } else {
241 strings.push(dimension.toString())
242 }
243 }
244
245 return '[' + strings.join(', ') + ']'
246 }
247
248 /**
249 * Get a JSON representation of the Index
250 * @memberof Index
251 * @returns {Object} Returns a JSON object structured as:
252 * `{"mathjs": "Index", "ranges": [{"mathjs": "Range", start: 0, end: 10, step:1}, ...]}`
253 */
254 Index.prototype.toJSON = function () {
255 return {
256 mathjs: 'Index',
257 dimensions: this._dimensions
258 }
259 }
260
261 /**
262 * Instantiate an Index from a JSON object
263 * @memberof Index
264 * @param {Object} json A JSON object structured as:
265 * `{"mathjs": "Index", "dimensions": [{"mathjs": "Range", start: 0, end: 10, step:1}, ...]}`
266 * @return {Index}
267 */
268 Index.fromJSON = function (json) {
269 return Index.create(json.dimensions)
270 }
271
272 return Index
273}
274
275exports.name = 'Index'
276exports.path = 'type'
277exports.factory = factory