1 |
|
2 |
|
3 | const number = require('../../utils/number')
|
4 |
|
5 | function 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 |
|
316 | exports.name = 'Range'
|
317 | exports.path = 'type'
|
318 | exports.factory = factory
|