1 | import { isBigNumber } from '../../utils/is'
|
2 | import { format, sign } from '../../utils/number'
|
3 | import { factory } from '../../utils/factory'
|
4 |
|
5 | const name = 'Range'
|
6 | const dependencies = []
|
7 |
|
8 | export 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 })
|