Created
May 6, 2018 16:08
-
-
Save julianjensen/b360f41404c1758e3d4fa8666a611525 to your computer and use it in GitHub Desktop.
Various ways of counting vowels with benchmarks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** ****************************************************************************************************************** | |
| * @file This just counts the number of vowels in some text. | |
| * | |
| * To install and play with this, do the following: | |
| * | |
| * 1. `mkdir vowel-count` | |
| * 2. `cd vowel-count` | |
| * 3. `npm init` | |
| * 4. Copy this file into the directory, call it anything you like, but `index.js` is normal. | |
| * 5. `npm i benchmark cli-table2` | |
| * 6. `node index.js` | |
| * | |
| * It does a while to run but it should show you some updates as it runs. | |
| * | |
| * @author Julian Jensen <jjdanois@gmail.com> | |
| * @since 1.0.0 | |
| * @date 06-May-2018 | |
| *********************************************************************************************************************/ | |
| "use strict"; | |
| const | |
| assert = require( 'assert' ), | |
| Table = require( 'cli-table2' ), | |
| table = { | |
| _table: new Table( { head: [ 'Name', 'ops/sec', 'MoE', 'Runs', 'Uses spread?' ] } ), | |
| add( rows ) | |
| { | |
| if ( rows && rows.length ) this._table.push( ...rows ); | |
| return this; | |
| }, | |
| show( rows ) | |
| { | |
| this.add( rows ); | |
| console.log( '\r' + this._table.toString() ); | |
| return this; | |
| } | |
| }, | |
| Benchmark = require( 'benchmark' ), | |
| suite = new Benchmark.Suite(), | |
| sentence = "No gangster would hold on to a gun after shooting someone (even lowly Spider) with it. Real-life Tommy would have disposed of the revolver and " + | |
| "just picked up another gun. In this case, the M1911, but it could have been any appropriate carry gun. I like it when the little details are covered.", | |
| preSplit = sentence.split( '' ), | |
| print = s => process.stdout.write( '\r' + s + '\r' ), | |
| fix = ( num, n = 2 ) => num.toFixed( n ), | |
| sort_results = ( { stats: a }, { stats: b } ) => a.mean + a.moe > b.mean + b.moe ? 1 : -1, | |
| formatNumber = num => { | |
| const [ int, frac ] = String( num ).split( '.' ); | |
| return int.replace( /(?=(?:\d{3})+$)(?!\b)/g, ',' ) + ( frac ? '.' + frac : '' ); | |
| }, | |
| to_row = ( { hz, id, stats: { sample, rme }, name: result = id } ) => | |
| [ result, { hAlign: 'right', content: formatNumber( fix( hz, hz < 100 ? 2 : 0 ) ) }, `\xb1${fix( rme )}%`, sample.length, { hAlign: 'center', content: /spread/.test( result ) ? 'Yes' : 'No' } ], | |
| show_cycle = ( { target } ) => print( String( target ) + ' '.repeat( 40 ) ), // process.stdout.write( '\r' + String( target ) + ' '.repeat( 40 ) + '\r' ), | |
| alphabet = [ 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], | |
| vowels = [ 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' ], | |
| vowelMap = { a: 1, e: 1, i: 1, o: 1, u: 1, A: 1, E: 1, I: 1, O: 1, U: 1 }; | |
| /** | |
| * Your basic standard `for` loop. | |
| * | |
| * @param {string} s | |
| * @return {number} | |
| */ | |
| function using_basic_for_loop( s ) | |
| { | |
| let count = 0; | |
| for ( let index = 0, len = s.length; index < len; ++index ) | |
| { | |
| const ch = s[ index ].toLowerCase(); | |
| if ( ch === 'a' || ch === 'e' || ch === 'i' || ch === 'o' || ch === 'u' ) | |
| ++count; | |
| } | |
| return count; | |
| } | |
| /** | |
| * This has more of a JavaScript flavor to it, using the string as an iterable. | |
| * | |
| * @param {string} s | |
| * @return {number} | |
| */ | |
| function using_string_as_iterable( s ) | |
| { | |
| let count = 0; | |
| for ( const ch of s ) | |
| vowels.includes( ch ) && count++; | |
| return count; | |
| } | |
| /** | |
| * Very JavaScript-y but uses spread with is still a performance killer. | |
| * | |
| * @param {string} s | |
| * @return {number} | |
| */ | |
| function idiomatic_foreach_with_spread( s ) | |
| { | |
| let count = 0; | |
| [ ...s ].forEach( ch => vowels.includes( ch ) && count++ ); | |
| return count; | |
| } | |
| /** | |
| * Very JavaScript-y but uses `split()` instead of spread and therefore should perform better. | |
| * | |
| * @param {string} s | |
| * @return {number} | |
| */ | |
| function idiomatic_foreach_with_split( s ) | |
| { | |
| let count = 0; | |
| s.split( '' ).forEach( ch => vowels.includes( ch ) && count++ ); | |
| return count; | |
| } | |
| /** | |
| * The obvious `reduce()` variation with the spread ball-and-chain dragging it down. | |
| * | |
| * @param {string} s | |
| * @return {number} | |
| */ | |
| function using_reduce_spread( s ) | |
| { | |
| return [ ...s ].reduce( ( cnt, ch ) => ( vowelMap[ ch ] || 0 ) + cnt, 0 ); | |
| } | |
| /** | |
| * The obvious `reduce()` without the spread. | |
| * | |
| * @param {string} s | |
| * @return {number} | |
| */ | |
| function using_reduce_split( s ) | |
| { | |
| return s.split( '' ).reduce( ( cnt, ch ) => ( vowelMap[ ch ] || 0 ) + cnt, 0 ); | |
| } | |
| const | |
| using_reduce_arrow_spread = s => [ ...s ].reduce( ( cnt, ch ) => ( vowelMap[ ch ] || 0 ) + cnt, 0 ), | |
| using_reduce_arrow_split = s => s.split( '' ).reduce( ( cnt, ch ) => ( vowelMap[ ch ] || 0 ) + cnt, 0 ), | |
| using_reduce_arrow_from = s => Array.from( s ).reduce( ( cnt, ch ) => ( vowelMap[ ch ] || 0 ) + cnt, 0 ), | |
| using_replace = s => s.replace( /[^aeiou]/gi, '' ).length, | |
| using_reduce_and_spread = s => [ ...s ].reduce( ( count, ch ) => | |
| count + ( ( ch = ch.charCodeAt( 0 ) ) & 0x80 || ch < 0x41 ? 0 : [ 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ][ ( ch | 0x20 ) - 0x61 ] ), 0 ), | |
| using_reduce_and_split = s => s.split( '' ).reduce( ( count, ch ) => | |
| count + ( ( ch = ch.charCodeAt( 0 ) ) & 0x80 || ch < 0x41 ? 0 : [ 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ][ ( ch | 0x20 ) - 0x61 ] ), 0 ), | |
| using_reducer_and_presplit_array_alphabet = () => preSplit.reduce( ( count, ch ) => | |
| count + ( ( ch = ch.charCodeAt( 0 ) ) & 0x80 || ch < 0x41 ? 0 : alphabet[ ( ch | 0x20 ) - 0x61 ] ), 0 ), | |
| using_reducer_and_array_split_alphabet = s => s.split( '' ).reduce( ( count, ch ) => | |
| count + ( ( ch = ch.charCodeAt( 0 ) ) & 0x80 || ch < 0x41 ? 0 : alphabet[ ( ch | 0x20 ) - 0x61 ] ), 0 ), | |
| using_reducer_split_and_conditional = s => s.split( '' ).reduce( ( count, ch ) => count + ( ( ch = ch.toLowerCase() ) === 'a' || ch === 'e' || ch === 'i' || ch === 'o' || ch === 'u' ), 0 ), | |
| using_reducer_split_and_conditional_alternate = s => s.split( '' ).reduce( ( count, ch ) => ( ch = ch.toLowerCase() ) === 'a' || ch === 'e' || ch === 'i' || ch === 'o' || ch === 'u' ? count + 1 : count, 0 ), | |
| // All the various functions | |
| funcs = [ | |
| using_basic_for_loop, | |
| using_string_as_iterable, | |
| idiomatic_foreach_with_spread, | |
| idiomatic_foreach_with_split, | |
| using_reduce_spread, | |
| using_reduce_split, | |
| using_reduce_arrow_split, | |
| using_reduce_arrow_spread, | |
| using_reduce_arrow_from, | |
| using_replace, | |
| using_reduce_and_spread, | |
| using_reduce_and_split, | |
| using_reducer_and_presplit_array_alphabet, | |
| using_reducer_and_array_split_alphabet, | |
| using_reducer_split_and_conditional, | |
| using_reducer_split_and_conditional_alternate | |
| ]; | |
| // assert that they all work and we didn't screw anything up | |
| funcs.forEach( fn => assert( fn( sentence ) === 89 ) ); | |
| // Add them all to the benchmarking suite | |
| funcs.forEach( fn => suite.add( fn.name.replace( /_/g, ' ' ), fn.bind( null, sentence ) ) ); | |
| process.stdout.write( `Starting the benchmark suite...\r` ); | |
| suite | |
| .on( 'cycle', show_cycle ) // Show result of each test as it finishes, overwriting the previous results | |
| .on( 'complete', // We're all done, show a nice sorted table of all the results | |
| function() { table.show( this.filter( 'successful' ).sort( sort_results ).map( to_row ) ); } ) | |
| .run( { async: true } ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment