1 | import { isBigNumber, isCollection, isNumber } from '../../utils/is'
|
2 | import { isInteger } from '../../utils/number'
|
3 | import { flatten } from '../../utils/array'
|
4 | import { factory } from '../../utils/factory'
|
5 |
|
6 | const name = 'quantileSeq'
|
7 | const dependencies = ['typed', 'add', 'multiply', 'partitionSelect', 'compare']
|
8 |
|
9 | export const createQuantileSeq = factory(name, dependencies, ({ typed, add, multiply, partitionSelect, compare }) => {
|
10 | |
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | function quantileSeq (data, probOrN, sorted) {
|
45 | let probArr, dataArr, one
|
46 |
|
47 | if (arguments.length < 2 || arguments.length > 3) {
|
48 | throw new SyntaxError('Function quantileSeq requires two or three parameters')
|
49 | }
|
50 |
|
51 | if (isCollection(data)) {
|
52 | sorted = sorted || false
|
53 | if (typeof sorted === 'boolean') {
|
54 | dataArr = data.valueOf()
|
55 | if (isNumber(probOrN)) {
|
56 | if (probOrN < 0) {
|
57 | throw new Error('N/prob must be non-negative')
|
58 | }
|
59 |
|
60 | if (probOrN <= 1) {
|
61 |
|
62 | return _quantileSeq(dataArr, probOrN, sorted)
|
63 | }
|
64 |
|
65 | if (probOrN > 1) {
|
66 |
|
67 | if (!isInteger(probOrN)) {
|
68 | throw new Error('N must be a positive integer')
|
69 | }
|
70 |
|
71 | const nPlusOne = probOrN + 1
|
72 | probArr = new Array(probOrN)
|
73 | for (let i = 0; i < probOrN;) {
|
74 | probArr[i] = _quantileSeq(dataArr, (++i) / nPlusOne, sorted)
|
75 | }
|
76 | return probArr
|
77 | }
|
78 | }
|
79 |
|
80 | if (isBigNumber(probOrN)) {
|
81 | const BigNumber = probOrN.constructor
|
82 |
|
83 | if (probOrN.isNegative()) {
|
84 | throw new Error('N/prob must be non-negative')
|
85 | }
|
86 |
|
87 | one = new BigNumber(1)
|
88 |
|
89 | if (probOrN.lte(one)) {
|
90 |
|
91 | return new BigNumber(_quantileSeq(dataArr, probOrN, sorted))
|
92 | }
|
93 |
|
94 | if (probOrN.gt(one)) {
|
95 |
|
96 | if (!probOrN.isInteger()) {
|
97 | throw new Error('N must be a positive integer')
|
98 | }
|
99 |
|
100 |
|
101 |
|
102 | const intN = probOrN.toNumber()
|
103 | if (intN > 4294967295) {
|
104 | throw new Error('N must be less than or equal to 2^32-1, as that is the maximum length of an Array')
|
105 | }
|
106 |
|
107 | const nPlusOne = new BigNumber(intN + 1)
|
108 | probArr = new Array(intN)
|
109 | for (let i = 0; i < intN;) {
|
110 | probArr[i] = new BigNumber(_quantileSeq(dataArr, new BigNumber(++i).div(nPlusOne), sorted))
|
111 | }
|
112 | return probArr
|
113 | }
|
114 | }
|
115 |
|
116 | if (Array.isArray(probOrN)) {
|
117 |
|
118 | probArr = new Array(probOrN.length)
|
119 | for (let i = 0; i < probArr.length; ++i) {
|
120 | const currProb = probOrN[i]
|
121 | if (isNumber(currProb)) {
|
122 | if (currProb < 0 || currProb > 1) {
|
123 | throw new Error('Probability must be between 0 and 1, inclusive')
|
124 | }
|
125 | } else if (isBigNumber(currProb)) {
|
126 | one = new currProb.constructor(1)
|
127 | if (currProb.isNegative() || currProb.gt(one)) {
|
128 | throw new Error('Probability must be between 0 and 1, inclusive')
|
129 | }
|
130 | } else {
|
131 | throw new TypeError('Unexpected type of argument in function quantileSeq')
|
132 | }
|
133 |
|
134 | probArr[i] = _quantileSeq(dataArr, currProb, sorted)
|
135 | }
|
136 | return probArr
|
137 | }
|
138 |
|
139 | throw new TypeError('Unexpected type of argument in function quantileSeq')
|
140 | }
|
141 |
|
142 | throw new TypeError('Unexpected type of argument in function quantileSeq')
|
143 | }
|
144 |
|
145 | throw new TypeError('Unexpected type of argument in function quantileSeq')
|
146 | }
|
147 |
|
148 | |
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | function _quantileSeq (array, prob, sorted) {
|
158 | const flat = flatten(array)
|
159 | const len = flat.length
|
160 | if (len === 0) {
|
161 | throw new Error('Cannot calculate quantile of an empty sequence')
|
162 | }
|
163 |
|
164 | if (isNumber(prob)) {
|
165 | const index = prob * (len - 1)
|
166 | const fracPart = index % 1
|
167 | if (fracPart === 0) {
|
168 | const value = sorted ? flat[index] : partitionSelect(flat, index)
|
169 |
|
170 | validate(value)
|
171 |
|
172 | return value
|
173 | }
|
174 |
|
175 | const integerPart = Math.floor(index)
|
176 |
|
177 | let left
|
178 | let right
|
179 | if (sorted) {
|
180 | left = flat[integerPart]
|
181 | right = flat[integerPart + 1]
|
182 | } else {
|
183 | right = partitionSelect(flat, integerPart + 1)
|
184 |
|
185 |
|
186 | left = flat[integerPart]
|
187 | for (let i = 0; i < integerPart; ++i) {
|
188 | if (compare(flat[i], left) > 0) {
|
189 | left = flat[i]
|
190 | }
|
191 | }
|
192 | }
|
193 |
|
194 | validate(left)
|
195 | validate(right)
|
196 |
|
197 |
|
198 | return add(multiply(left, 1 - fracPart), multiply(right, fracPart))
|
199 | }
|
200 |
|
201 |
|
202 | let index = prob.times(len - 1)
|
203 | if (index.isInteger()) {
|
204 | index = index.toNumber()
|
205 | const value = sorted ? flat[index] : partitionSelect(flat, index)
|
206 |
|
207 | validate(value)
|
208 |
|
209 | return value
|
210 | }
|
211 |
|
212 | const integerPart = index.floor()
|
213 | const fracPart = index.minus(integerPart)
|
214 | const integerPartNumber = integerPart.toNumber()
|
215 |
|
216 | let left
|
217 | let right
|
218 | if (sorted) {
|
219 | left = flat[integerPartNumber]
|
220 | right = flat[integerPartNumber + 1]
|
221 | } else {
|
222 | right = partitionSelect(flat, integerPartNumber + 1)
|
223 |
|
224 |
|
225 | left = flat[integerPartNumber]
|
226 | for (let i = 0; i < integerPartNumber; ++i) {
|
227 | if (compare(flat[i], left) > 0) {
|
228 | left = flat[i]
|
229 | }
|
230 | }
|
231 | }
|
232 |
|
233 | validate(left)
|
234 | validate(right)
|
235 |
|
236 |
|
237 | const one = new fracPart.constructor(1)
|
238 | return add(multiply(left, one.minus(fracPart)), multiply(right, fracPart))
|
239 | }
|
240 |
|
241 | |
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | const validate = typed({
|
248 | 'number | BigNumber | Unit': function (x) {
|
249 | return x
|
250 | }
|
251 | })
|
252 |
|
253 | return quantileSeq
|
254 | })
|