@@ -178,6 +178,115 @@ describe("Mathematical functions", () => {
178
178
it ( "measures empty distances" , ( ) => expect ( distance ( [ 32 , 4 ] , [ 32 , 4 ] ) ) . to . equal ( 0 ) ) ;
179
179
} ) ;
180
180
181
+ describe ( "mean()" , ( ) => {
182
+ const { mean} = utils ;
183
+ it ( "averages sorted values" , ( ) => {
184
+ expect ( mean ( 1 , 2 , 3 ) ) . to . equal ( 2 ) ;
185
+ expect ( mean ( 0 , 10 ) ) . to . equal ( 5 ) ;
186
+ expect ( mean ( - 5 , 5 ) ) . to . equal ( 0 ) ;
187
+ expect ( mean ( 1n , 5n ) ) . to . equal ( 3 ) ;
188
+ expect ( mean ( 1 , 10n ) ) . to . equal ( 5.5 ) ;
189
+ } ) ;
190
+ it ( "averages unsorted values" , ( ) => {
191
+ expect ( mean ( 3 , 1 , 2 ) ) . to . equal ( 2 ) ;
192
+ expect ( mean ( 1024 , 0 ) ) . to . equal ( 512 ) ;
193
+ expect ( mean ( 5 , - 5 , 6 ) ) . to . equal ( 2 ) ;
194
+ expect ( mean ( 6 , 11 , 7 ) ) . to . equal ( 8 ) ;
195
+ expect ( mean ( 50n , 25n ) ) . to . equal ( 37.5 ) ;
196
+ expect ( mean ( 512n , 0 ) ) . to . equal ( 256 ) ;
197
+ } ) ;
198
+ it ( "accepts numeric strings as input" , ( ) => {
199
+ expect ( mean ( "1" , "3" ) ) . to . equal ( 2 ) ;
200
+ expect ( mean ( 1 , "100" ) ) . to . equal ( 50.5 ) ;
201
+ expect ( mean ( - 1n , "1" ) ) . to . equal ( 0 ) ;
202
+ expect ( mean ( "-5" , 0 ) ) . to . equal ( - 2.5 ) ;
203
+ expect ( mean ( 0n , "5" , 7 ) ) . to . equal ( 4 ) ;
204
+ } ) ;
205
+ } ) ;
206
+
207
+ describe ( "median()" , ( ) => {
208
+ const { median} = utils ;
209
+ const oddLists = [
210
+ [ [ 3 , 5 , 12 ] , 5 ] ,
211
+ [ [ 3 , 5 , 7 , 12 , 13 , 14 , 21 , 23 , 23 , 23 , 23 , 29 , 39 , 40 , 56 ] , 23 ] ,
212
+ [ [ 10 , 11 , 13 , 15 , 16 , 23 , 26 ] , 15 ] ,
213
+ ] ;
214
+ const evenLists = [
215
+ [ [ 1 , 2 ] , 1.5 ] ,
216
+ [ [ 1 , 3 , 4 , 5 ] , 3.5 ] ,
217
+ [ [ 3 , 5 , 7 , 12 , 13 , 14 , 21 , 23 , 23 , 23 , 23 , 29 , 40 , 56 ] , 22 ] ,
218
+ [ [ 0 , 50 , 60 , 100 ] , 55 ] ,
219
+ [ [ 23.5 , 24 ] , 23.75 ] ,
220
+ ] ;
221
+ it ( "retrieves an odd-sized list's middle value" , ( ) => {
222
+ for ( const [ input , expected ] of oddLists )
223
+ expect ( median ( ...numSort ( input ) ) ) . to . equal ( expected ) ;
224
+ } ) ;
225
+ it ( "averages an even-sized list's two middlemost values" , ( ) => {
226
+ for ( const [ input , expected ] of evenLists )
227
+ expect ( median ( ...numSort ( input ) ) ) . to . equal ( expected ) ;
228
+ } ) ;
229
+ it ( "doesn't require lists to be pre-sorted" , ( ) => {
230
+ for ( const [ input , expected ] of oddLists . concat ( evenLists ) ) {
231
+ const i = Math . round ( input . length / 2 ) ;
232
+ const a = input . slice ( 0 , i ) . reverse ( ) ;
233
+ const b = input . slice ( i ) . reverse ( ) ;
234
+ expect ( median ( ...b . concat ( a ) ) ) . to . equal ( expected ) ;
235
+ }
236
+ } ) ;
237
+ it ( "coerces non-numeric values" , ( ) => {
238
+ let calls = 0 ;
239
+ const num = 0xBABEFACE ;
240
+ const obj = {
241
+ __proto__ : null ,
242
+ toString : ( ) => "Invalid number" ,
243
+ valueOf : ( ) => ( ++ calls , num ) ,
244
+ } ;
245
+ expect ( median ( ...[ obj , 0 , num * 2 ] ) ) . to . equal ( num ) ;
246
+ expect ( calls ) . to . equal ( 1 ) ;
247
+ } ) ;
248
+ } ) ;
249
+
250
+ describe ( "mode()" , ( ) => {
251
+ const { mode} = utils ;
252
+ const lists = {
253
+ __proto__ : null ,
254
+ unimodal : [
255
+ [ [ 1 , 2 , 3 , 1 ] , 1 ] ,
256
+ [ [ 6 , 3 , 9 , 6 , 6 , 5 , 9 , 3 ] , 6 ] ,
257
+ [ [ 3 , 7 , 5 , 13 , 20 , 23 , 39 , 23 , 40 , 23 , 14 , 12 , 56 , 23 , 29 ] , 23 ] ,
258
+ [ [ 19 , 8 , 29 , 35 , 19 , 28 , 15 ] , 19 ] ,
259
+ ] ,
260
+ bimodal : [
261
+ [ [ 1 , 3 , 3 , 3 , 4 , 4 , 6 , 6 , 6 , 9 ] , [ 3 , 6 ] ] ,
262
+ [ [ 7 , 2 , 15 , 11 , 15 , 9 , 13 , 0 , 10 , 1 , 12 , 2 , 0 , 5 , 15 , 5 , 2 , 3 ] , [ 2 , 15 ] ] ,
263
+ ] ,
264
+ multimodal : [
265
+ [ [ 1 , 3 , 3 , 7 , 7 , 7 , 3 , 1 , 0 , 1 ] , [ 1 , 3 , 7 ] ] ,
266
+ [ [
267
+ 2.4 , 7 , 3.2 , - 3.5 , 0.8 , 0.9 , 2.6 , - 5.4 , 7 , 5.3 , 3.4 , 0.7 , - 1.2 , 6.4 ,
268
+ 7 , 2.3 , 1.3 , - 0.9 , 4.1 , 5.4 , 2.5 , 3.7 , 0.5 , 3.4 , 2.3 , 0.5 , - 1.4 , 3.6 ,
269
+ 0.8 , - 3.9 , 0.5 , - 0.3 , 1.5 , 0.8 ,
270
+ ] , [ 0.5 , 0.8 , 7 ] ] ,
271
+ ] ,
272
+ } ;
273
+ for ( const type of [ "unimodal" , "bimodal" , "multimodal" ] )
274
+ for ( const sort of [ "sorted" , "unsorted" ] )
275
+ it ( `isolates ${ type } distributions from ${ sort } lists` , ( ) => {
276
+ for ( let [ input , expected ] of lists [ type ] ) {
277
+ if ( "sorted" === sort ) input = numSort ( input ) ;
278
+ if ( "number" === typeof expected ) expected = [ expected ] ;
279
+ expect ( mode ( ...input ) ) . to . eql ( expected ) ;
280
+ }
281
+ } ) ;
282
+ it ( "returns modes in a predictable order" , ( ) => {
283
+ const input = [ - 10 , 8 , 8 , 8 , 0.1 , 0.1 , 0.1 , 7 , 7 , 7 , 10 ] ;
284
+ const expected = [ 0.1 , 7 , 8 ] ;
285
+ expect ( mode ( ...input ) ) . to . eql ( expected ) ;
286
+ expect ( mode ( ...numSort ( input ) ) ) . to . eql ( expected ) ;
287
+ } ) ;
288
+ } ) ;
289
+
181
290
describe ( "normalise()" , ( ) => {
182
291
const { normalise} = utils ;
183
292
const cmp = ( input , expected ) => {
@@ -480,4 +589,15 @@ describe("Mathematical functions", () => {
480
589
expect ( sum ( - 4 , 2n ) ) . to . equal ( - 2 ) ;
481
590
} ) ;
482
591
} ) ;
592
+
593
+ /**
594
+ * Return a copy of an array with entries sorted numerically.
595
+ * @example numSort([3, 200, 10]) == [3, 10, 200];
596
+ * @param {Array } list
597
+ * @return {Array }
598
+ * @internal
599
+ */
600
+ function numSort ( list ) {
601
+ return list . slice ( ) . sort ( ( a , b ) => a < b ? - 1 : a > b ? 1 : 0 ) ;
602
+ }
483
603
} ) ;
0 commit comments