UNPKG

8.27 kBJavaScriptView Raw
1'use strict'
2
3const number = require('../../utils/number')
4
5function factory (type, config, load, typed) {
6 /**
7 * Create a range. A range has a start, step, and end, and contains functions
8 * to iterate over the range.
9 *
10 * A range can be constructed as:
11 *
12 * const range = new Range(start, end)
13 * const range = new Range(start, end, step)
14 *
15 * To get the result of the range:
16 * range.forEach(function (x) {
17 * console.log(x)
18 * })
19 * range.map(function (x) {
20 * return math.sin(x)
21 * })
22 * range.toArray()
23 *
24 * Example usage:
25 *
26 * const c = new Range(2, 6) // 2:1:5
27 * c.toArray() // [2, 3, 4, 5]
28 * const d = new Range(2, -3, -1) // 2:-1:-2
29 * d.toArray() // [2, 1, 0, -1, -2]
30 *
31 * @class Range
32 * @constructor Range
33 * @param {number} start included lower bound
34 * @param {number} end excluded upper bound
35 * @param {number} [step] step size, default value is 1
36 */
37 function Range (start, end, step) {
38 if (!(this instanceof Range)) {
39 throw new SyntaxError('Constructor must be called with the new operator')
40 }
41
42 const hasStart = start !== null && start !== undefined
43 const hasEnd = end !== null && end !== undefined
44 const hasStep = step !== null && step !== undefined
45
46 if (hasStart) {
47 if (type.isBigNumber(start)) {
48 start = start.toNumber()
49 } else if (typeof start !== 'number') {
50 throw new TypeError('Parameter start must be a number')
51 }
52 }
53 if (hasEnd) {
54 if (type.isBigNumber(end)) {
55 end = end.toNumber()
56 } else if (typeof end !== 'number') {
57 throw new TypeError('Parameter end must be a number')
58 }
59 }
60 if (hasStep) {
61 if (type.isBigNumber(step)) {
62 step = step.toNumber()
63 } else if (typeof step !== 'number') {
64 throw new TypeError('Parameter step must be a number')
65 }
66 }
67
68 this.start = hasStart ? parseFloat(start) : 0
69 this.end = hasEnd ? parseFloat(end) : 0
70 this.step = hasStep ? parseFloat(step) : 1
71 }
72
73 /**
74 * Attach type information
75 */
76 Range.prototype.type = 'Range'
77 Range.prototype.isRange = true
78
79 /**
80 * Parse a string into a range,
81 * The string contains the start, optional step, and end, separated by a colon.
82 * If the string does not contain a valid range, null is returned.
83 * For example str='0:2:11'.
84 * @memberof Range
85 * @param {string} str
86 * @return {Range | null} range
87 */
88 Range.parse = function (str) {
89 if (typeof str !== 'string') {
90 return null
91 }
92
93 const args = str.split(':')
94 const nums = args.map(function (arg) {
95 return parseFloat(arg)
96 })
97
98 const invalid = nums.some(function (num) {
99 return isNaN(num)
100 })
101 if (invalid) {
102 return null
103 }
104
105 switch (nums.length) {
106 case 2:
107 return new Range(nums[0], nums[1])
108 case 3:
109 return new Range(nums[0], nums[2], nums[1])
110 default:
111 return null
112 }
113 }
114
115 /**
116 * Create a clone of the range
117 * @return {Range} clone
118 */
119 Range.prototype.clone = function () {
120 return new Range(this.start, this.end, this.step)
121 }
122
123 /**
124 * Retrieve the size of the range.
125 * Returns an array containing one number, the number of elements in the range.
126 * @memberof Range
127 * @returns {number[]} size
128 */
129 Range.prototype.size = function () {
130 let len = 0
131 const start = this.start
132 const step = this.step
133 const end = this.end
134 const diff = end - start
135
136 if (number.sign(step) === number.sign(diff)) {
137 len = Math.ceil((diff) / step)
138 } else if (diff === 0) {
139 len = 0
140 }
141
142 if (isNaN(len)) {
143 len = 0
144 }
145 return [len]
146 }
147
148 /**
149 * Calculate the minimum value in the range
150 * @memberof Range
151 * @return {number | undefined} min
152 */
153 Range.prototype.min = function () {
154 const size = this.size()[0]
155
156 if (size > 0) {
157 if (this.step > 0) {
158 // positive step
159 return this.start
160 } else {
161 // negative step
162 return this.start + (size - 1) * this.step
163 }
164 } else {
165 return undefined
166 }
167 }
168
169 /**
170 * Calculate the maximum value in the range
171 * @memberof Range
172 * @return {number | undefined} max
173 */
174 Range.prototype.max = function () {
175 const size = this.size()[0]
176
177 if (size > 0) {
178 if (this.step > 0) {
179 // positive step
180 return this.start + (size - 1) * this.step
181 } else {
182 // negative step
183 return this.start
184 }
185 } else {
186 return undefined
187 }
188 }
189
190 /**
191 * Execute a callback function for each value in the range.
192 * @memberof Range
193 * @param {function} callback The callback method is invoked with three
194 * parameters: the value of the element, the index
195 * of the element, and the Range being traversed.
196 */
197 Range.prototype.forEach = function (callback) {
198 let x = this.start
199 const step = this.step
200 const end = this.end
201 let i = 0
202
203 if (step > 0) {
204 while (x < end) {
205 callback(x, [i], this)
206 x += step
207 i++
208 }
209 } else if (step < 0) {
210 while (x > end) {
211 callback(x, [i], this)
212 x += step
213 i++
214 }
215 }
216 }
217
218 /**
219 * Execute a callback function for each value in the Range, and return the
220 * results as an array
221 * @memberof Range
222 * @param {function} callback The callback method is invoked with three
223 * parameters: the value of the element, the index
224 * of the element, and the Matrix being traversed.
225 * @returns {Array} array
226 */
227 Range.prototype.map = function (callback) {
228 const array = []
229 this.forEach(function (value, index, obj) {
230 array[index[0]] = callback(value, index, obj)
231 })
232 return array
233 }
234
235 /**
236 * Create an Array with a copy of the Ranges data
237 * @memberof Range
238 * @returns {Array} array
239 */
240 Range.prototype.toArray = function () {
241 const array = []
242 this.forEach(function (value, index) {
243 array[index[0]] = value
244 })
245 return array
246 }
247
248 /**
249 * Get the primitive value of the Range, a one dimensional array
250 * @memberof Range
251 * @returns {Array} array
252 */
253 Range.prototype.valueOf = function () {
254 // TODO: implement a caching mechanism for range.valueOf()
255 return this.toArray()
256 }
257
258 /**
259 * Get a string representation of the range, with optional formatting options.
260 * Output is formatted as 'start:step:end', for example '2:6' or '0:0.2:11'
261 * @memberof Range
262 * @param {Object | number | function} [options] Formatting options. See
263 * lib/utils/number:format for a
264 * description of the available
265 * options.
266 * @returns {string} str
267 */
268 Range.prototype.format = function (options) {
269 let str = number.format(this.start, options)
270
271 if (this.step !== 1) {
272 str += ':' + number.format(this.step, options)
273 }
274 str += ':' + number.format(this.end, options)
275 return str
276 }
277
278 /**
279 * Get a string representation of the range.
280 * @memberof Range
281 * @returns {string}
282 */
283 Range.prototype.toString = function () {
284 return this.format()
285 }
286
287 /**
288 * Get a JSON representation of the range
289 * @memberof Range
290 * @returns {Object} Returns a JSON object structured as:
291 * `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}`
292 */
293 Range.prototype.toJSON = function () {
294 return {
295 mathjs: 'Range',
296 start: this.start,
297 end: this.end,
298 step: this.step
299 }
300 }
301
302 /**
303 * Instantiate a Range from a JSON object
304 * @memberof Range
305 * @param {Object} json A JSON object structured as:
306 * `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}`
307 * @return {Range}
308 */
309 Range.fromJSON = function (json) {
310 return new Range(json.start, json.end, json.step)
311 }
312
313 return Range
314}
315
316exports.name = 'Range'
317exports.path = 'type'
318exports.factory = factory