$ node index.js
status quo x 183,314 ops/sec ±7.01% (81 runs sampled)
comma fix x 88,004 ops/sec ±5.18% (81 runs sampled)
alternate fix x 88,608 ops/sec ±4.89% (84 runs sampled)
regex match fix x 36,670 ops/sec ±5.04% (83 runs sampled)
iteration fix x 175,519 ops/sec ±5.20% (84 runs sampled)
nested x 88,474 ops/sec ±3.91% (82 runs sampled)
Last active
February 25, 2018 06:57
-
-
Save nickiaconis/85692900b4675b3d262e5d17d933a37d to your computer and use it in GitHub Desktop.
Expand Properties Benchmark
This file contains 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
// import { assert } from 'ember-metal/debug'; | |
/** | |
@module ember | |
@submodule ember-metal | |
*/ | |
var EXPAND_REGEX = /\{([^}]*)\}/; | |
var END_WITH_EACH_REGEX = /\.@each$/; | |
/** | |
Expands `pattern`, invoking `callback` for each expansion. | |
The only pattern supported is brace-expansion, anything else will be passed | |
once to `callback` directly. | |
Example | |
```js | |
function echo(arg){ console.log(arg); } | |
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' | |
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' | |
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' | |
Ember.expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' | |
Ember.expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' | |
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' | |
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' | |
``` | |
@method expandProperties | |
@for Ember | |
@private | |
@param {String} pattern The property pattern to expand. | |
@param {Function} callback The callback to invoke. It is invoked once per | |
expansion, and is passed the expansion. | |
*/ | |
export default function expandProperties(pattern, callback) { | |
// assert('A computed property key must be a string', typeof pattern === 'string'); | |
// assert( | |
// 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', | |
// pattern.indexOf(' ') === -1 | |
// ); | |
var properties = [pattern]; | |
for (let property, i = 0; i < properties.length; i++) { | |
for (let match; (property = properties[i]) && (match = EXPAND_REGEX.exec(property));) { | |
properties.splice(i, 1, ...duplicateAndReplace(property, match)); | |
} | |
} | |
for (let i = 0; i < properties.length; i++) { | |
callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]')); | |
} | |
} | |
function duplicateAndReplace(property, match) { | |
var all = []; | |
var parts = match[1].split(','); | |
parts.forEach((part) => { | |
all.push(property.substring(0, match.index) + | |
part + | |
property.substring(match.index + match[0].length)); | |
}); | |
return all; | |
} |
This file contains 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
// import { assert } from 'ember-metal/debug'; | |
/** | |
@module ember | |
@submodule ember-metal | |
*/ | |
var SPLIT_REGEX = /\{|\}/; | |
var END_WITH_EACH_REGEX = /\.@each$/; | |
/** | |
Expands `pattern`, invoking `callback` for each expansion. | |
The only pattern supported is brace-expansion, anything else will be passed | |
once to `callback` directly. | |
Example | |
```js | |
function echo(arg){ console.log(arg); } | |
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' | |
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' | |
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' | |
Ember.expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' | |
Ember.expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' | |
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' | |
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' | |
``` | |
@method expandProperties | |
@for Ember | |
@private | |
@param {String} pattern The property pattern to expand. | |
@param {Function} callback The callback to invoke. It is invoked once per | |
expansion, and is passed the expansion. | |
*/ | |
export default function expandProperties(pattern, callback) { | |
// assert('A computed property key must be a string', typeof pattern === 'string'); | |
// assert( | |
// 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', | |
// pattern.indexOf(' ') === -1 | |
// ); | |
var parts = pattern.split(SPLIT_REGEX); | |
var properties = [parts]; | |
for (let i = 0; i < parts.length; i++) { | |
let part = parts[i]; | |
if (part.indexOf(',') >= 0) { | |
properties = duplicateAndReplace(properties, part.split(','), i); | |
} | |
} | |
for (let i = 0; i < properties.length; i++) { | |
callback(properties[i].join('').replace(END_WITH_EACH_REGEX, '.[]')); | |
} | |
} | |
function duplicateAndReplace(properties, currentParts, index) { | |
var all = []; | |
properties.forEach((property) => { | |
currentParts.forEach((part) => { | |
var current = property.slice(0); | |
current[index] = part; | |
all.push(current); | |
}); | |
}); | |
return all; | |
} |
This file contains 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
// import { assert } from 'ember-metal/debug'; | |
/** | |
@module ember | |
@submodule ember-metal | |
*/ | |
var EXPAND_REGEX = /\{([^{}]*)\}/; | |
var END_WITH_EACH_REGEX = /\.@each$/; | |
/** | |
Expands `pattern`, invoking `callback` for each expansion. | |
The only pattern supported is brace-expansion, anything else will be passed | |
once to `callback` directly. | |
Example | |
```js | |
function echo(arg){ console.log(arg); } | |
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' | |
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' | |
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' | |
Ember.expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' | |
Ember.expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' | |
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' | |
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' | |
``` | |
@method expandProperties | |
@for Ember | |
@private | |
@param {String} pattern The property pattern to expand. | |
@param {Function} callback The callback to invoke. It is invoked once per | |
expansion, and is passed the expansion. | |
*/ | |
export default function expandProperties(pattern, callback) { | |
// assert('A computed property key must be a string', typeof pattern === 'string'); | |
// assert( | |
// 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', | |
// pattern.indexOf(' ') === -1 | |
// ); | |
var properties = [pattern]; | |
for (let property, i = 0; i < properties.length; i++) { | |
for (let match; (property = properties[i]) && (match = EXPAND_REGEX.exec(property));) { | |
properties.splice(i, 1, ...duplicateAndReplace(property, match)); | |
} | |
} | |
for (let i = 0; i < properties.length; i++) { | |
callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]')); | |
} | |
} | |
function duplicateAndReplace(property, match) { | |
var all = []; | |
var parts = match[1].split(','); | |
parts.forEach((part) => { | |
all.push(property.substring(0, match.index) + | |
part + | |
property.substring(match.index + match[0].length)); | |
}); | |
return all; | |
} |
This file contains 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
var Benchmark = require('benchmark'); | |
var babel = require('babel-core'); | |
var fs = require('fs'); | |
var BENCHMARK_STRING = 'model.{a,b,c}.d.{e,f}.g'; | |
function K() {} | |
var before = eval(babel.transform(fs.readFileSync('./before.js')).code); | |
var comma = eval(babel.transform(fs.readFileSync('./comma.js')).code); | |
var alternate = eval(babel.transform(fs.readFileSync('./alternate.js')).code); | |
var matches = eval(babel.transform(fs.readFileSync('./matches.js')).code); | |
var iteration = eval(babel.transform(fs.readFileSync('./iteration.js')).code); | |
var nested = eval(babel.transform(fs.readFileSync('./nested.js')).code); | |
var suite = new Benchmark.Suite('expand properties'); | |
suite.add('status quo', function () { | |
before(BENCHMARK_STRING, K); | |
}); | |
suite.add('comma fix', function() { | |
comma(BENCHMARK_STRING, K); | |
}); | |
suite.add('alternate fix', function() { | |
alternate(BENCHMARK_STRING, K); | |
}); | |
suite.add('regex match fix', function() { | |
matches(BENCHMARK_STRING, K); | |
}); | |
suite.add('iteration fix', function() { | |
iteration(BENCHMARK_STRING, K); | |
}); | |
suite.add('nested', function () { | |
nested(BENCHMARK_STRING, K); | |
}); | |
suite.on('cycle', function(event) { | |
console.log(String(event.target)); | |
}); | |
suite.run({ async: true }); |
This file contains 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
// import { assert } from 'ember-metal/debug'; | |
/** | |
@module ember | |
@submodule ember-metal | |
*/ | |
var END_WITH_EACH_REGEX = /\.@each$/; | |
/** | |
Expands `pattern`, invoking `callback` for each expansion. | |
The only pattern supported is brace-expansion, anything else will be passed | |
once to `callback` directly. | |
Example | |
```js | |
function echo(arg){ console.log(arg); } | |
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' | |
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' | |
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' | |
Ember.expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' | |
Ember.expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' | |
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' | |
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' | |
``` | |
@method expandProperties | |
@for Ember | |
@private | |
@param {String} pattern The property pattern to expand. | |
@param {Function} callback The callback to invoke. It is invoked once per | |
expansion, and is passed the expansion. | |
*/ | |
export default function expandProperties(pattern, callback) { | |
// assert('A computed property key must be a string', typeof pattern === 'string'); | |
// assert( | |
// 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', | |
// pattern.indexOf(' ') === -1 | |
// ); | |
const properties = [pattern]; | |
let bookmark, inside = false; | |
for (let i = pattern.length; i > 0; --i) { | |
let current = pattern[i - 1]; | |
switch(current) { | |
case '}': | |
if (!inside) { | |
bookmark = i - 1; | |
inside = true; | |
} | |
break; | |
case '{': | |
if (inside) { | |
const expansion = pattern.slice(i, bookmark).split(','); | |
for (let j = properties.length; j > 0; --j) { | |
let [property] = properties.splice(j - 1, 1); | |
for (let k = 0; k < expansion.length; ++k) { | |
properties.push(property.slice(0, i - 1) + | |
expansion[k] + | |
property.slice(bookmark + 1)); | |
} | |
} | |
inside = false; | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
for (let i = 0; i < properties.length; i++) { | |
callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]')); | |
} | |
} |
This file contains 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
// import { assert } from 'ember-metal/debug'; | |
/** | |
@module ember | |
@submodule ember-metal | |
*/ | |
var EXPAND_REGEX = /\{([^}]*)\}/g; | |
var END_WITH_EACH_REGEX = /\.@each$/; | |
/** | |
Expands `pattern`, invoking `callback` for each expansion. | |
The only pattern supported is brace-expansion, anything else will be passed | |
once to `callback` directly. | |
Example | |
```js | |
function echo(arg){ console.log(arg); } | |
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' | |
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' | |
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' | |
Ember.expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' | |
Ember.expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' | |
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' | |
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' | |
``` | |
@method expandProperties | |
@for Ember | |
@private | |
@param {String} pattern The property pattern to expand. | |
@param {Function} callback The callback to invoke. It is invoked once per | |
expansion, and is passed the expansion. | |
*/ | |
export default function expandProperties(pattern, callback) { | |
// assert('A computed property key must be a string', typeof pattern === 'string'); | |
// assert( | |
// 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', | |
// pattern.indexOf(' ') === -1 | |
// ); | |
const processed = [pattern]; | |
let match; | |
let slack = 0; | |
while(match = EXPAND_REGEX.exec(pattern)) { | |
let focus = processed.pop(); | |
processed.push(focus.slice(0, match.index - slack)); | |
processed.push(match[1].split(',')); | |
processed.push(focus.substring(match.index + match[0].length - slack)); | |
slack = match.index + match[0].length; | |
} | |
const properties = [processed[0]]; | |
for (let i = 1; i < processed.length; ++i) { | |
let strOrArray = processed[i]; | |
for (let j = properties.length; j > 0; --j) { | |
if (typeof strOrArray === 'string') { | |
properties[j - 1] += strOrArray; | |
} else { | |
let currentProperty = properties[j - 1]; | |
properties.splice(j - 1, 1, ...strOrArray.map((str) => currentProperty + str)); | |
} | |
} | |
} | |
for (let i = 0; i < properties.length; i++) { | |
callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]')); | |
} | |
} |
This file contains 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
// import { assert } from 'ember-metal/debug'; | |
/** | |
@module ember | |
@submodule ember-metal | |
*/ | |
var END_WITH_EACH_REGEX = /\.@each$/; | |
/** | |
Expands `pattern`, invoking `callback` for each expansion. | |
The only pattern supported is brace-expansion, anything else will be passed | |
once to `callback` directly. | |
Example | |
```js | |
function echo(arg){ console.log(arg); } | |
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' | |
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' | |
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' | |
Ember.expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' | |
Ember.expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' | |
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' | |
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' | |
Ember.expandProperties('foo.{bar.{biz,baz},[]}', echo) //=> 'foo.bar.biz', 'foo.bar.baz', 'foo.[]' | |
``` | |
@method expandProperties | |
@for Ember | |
@private | |
@param {String} pattern The property pattern to expand. | |
@param {Function} callback The callback to invoke. It is invoked once per | |
expansion, and is passed the expansion. | |
*/ | |
export default function expandProperties(pattern, callback) { | |
// assert('A computed property key must be a string', typeof pattern === 'string'); | |
// assert( | |
// 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', | |
// pattern.indexOf(' ') === -1 | |
// ); | |
var properties = [pattern]; | |
for (let property, i = 0; i < properties.length; i++) { | |
for (let match; (property = properties[i]) && (match = matchOuterMostBraces(property));) { | |
properties.splice(i, 1, ...duplicateAndReplace(property, match)); | |
} | |
} | |
for (let i = 0; i < properties.length; i++) { | |
callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]')); | |
} | |
} | |
function duplicateAndReplace(property, match) { | |
var all = []; | |
var parts = splitOutsideBraces(match[1], ','); | |
parts.forEach((part) => { | |
all.push(property.substring(0, match.index) + | |
part + | |
property.substring(match.index + match[0].length)); | |
}); | |
return all; | |
} | |
function splitOutsideBraces(string, splitChar) { | |
var parts = []; | |
// search for commas to split on | |
for (var stack = 0, start = 0, end = 0; end < string.length; ++end) { | |
switch (string[end]) { | |
case '{': | |
// enter brace expansion | |
++stack; | |
break; | |
case '}': | |
// exit brace expansion | |
stack = Math.max(stack - 1, 0); | |
break; | |
case splitChar[0]: | |
// split on given character, but only if not inside a brace expansion | |
// then restart search after the character | |
if (!stack) { | |
parts.push(string.slice(start, end)); | |
start = end + 1; | |
} | |
break; | |
default: | |
// any other character is a no-op | |
break; | |
} | |
} | |
// add the last part | |
if (start !== end) { | |
parts.push(string.slice(start, end)); | |
} | |
return parts; | |
} | |
function matchOuterMostBraces(string) { | |
let openIndex = string.indexOf('{'); | |
// ensure there is at least one brace expansion | |
if (openIndex < 0) { | |
return null; | |
} | |
// search for the matching brace for this expansion | |
let closeIndex = openIndex; | |
for (let stack = 1; stack > 0 && ++closeIndex < string.length;) { | |
switch (string[closeIndex]) { | |
case '{': | |
// enter brace expansion | |
++stack; | |
break; | |
case '}': | |
// exit brace expansion | |
--stack; | |
break; | |
default: | |
// any other character is a no-op | |
break; | |
} | |
} | |
// ensure there is a matching brace | |
if (closeIndex >= string.length) { | |
return null; | |
} | |
// simulate a regex match object, with a capturing group inside the outer braces | |
let match = [string.slice(openIndex, closeIndex + 1), string.slice(openIndex + 1, closeIndex)]; | |
match.index = openIndex; | |
match.input = string; | |
return match; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment