UNPKG

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