Skip to content

Instantly share code, notes, and snippets.

@ldong
Forked from nickiaconis/RESULTS.md
Created February 25, 2018 06:57
Show Gist options
  • Select an option

  • Save ldong/2bd9e96e2c7f3a1cd290f5d3194e8049 to your computer and use it in GitHub Desktop.

Select an option

Save ldong/2bd9e96e2c7f3a1cd290f5d3194e8049 to your computer and use it in GitHub Desktop.

Revisions

  1. ldong renamed this gist Feb 25, 2018. 1 changed file with 0 additions and 0 deletions.
  2. @nickiaconis nickiaconis revised this gist Apr 11, 2016. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions RESULTS.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    > $ 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)
  3. @nickiaconis nickiaconis revised this gist Apr 11, 2016. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions RESULTS.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    > 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)
    > 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)
  4. @nickiaconis nickiaconis created this gist Apr 11, 2016.
    6 changes: 6 additions & 0 deletions RESULTS.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    > 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)
    69 changes: 69 additions & 0 deletions alternate.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    // 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;
    }
    72 changes: 72 additions & 0 deletions before.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    // 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;
    }
    69 changes: 69 additions & 0 deletions comma.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    // 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;
    }
    46 changes: 46 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    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 });
    79 changes: 79 additions & 0 deletions iteration.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    // 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, '.[]'));
    }
    }
    74 changes: 74 additions & 0 deletions matches.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    // 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, '.[]'));
    }
    }
    144 changes: 144 additions & 0 deletions nested.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,144 @@
    // 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;
    }