From 35bb2fec4d3ecc76ce136981714780d603d0e95b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 17 Jan 2022 10:21:41 +0100 Subject: [PATCH] fix: query onestep with non zero start --- dist/pattern.js | 470 ++++++++++++++++++++++++------------------- src/queryArc.js | 5 +- test/test-pattern.js | 27 +++ 3 files changed, 298 insertions(+), 204 deletions(-) create mode 100644 test/test-pattern.js diff --git a/dist/pattern.js b/dist/pattern.js index 4d7f991..df8eb96 100644 --- a/dist/pattern.js +++ b/dist/pattern.js @@ -2421,10 +2421,10 @@ module.exports = function(m, k){ },{}],4:[function(require,module,exports){ /** - * @license Fraction.js v4.0.12 09/09/2015 - * http://www.xarg.org/2014/03/rational-numbers-in-javascript/ + * @license Fraction.js v4.1.2 23/05/2021 + * https://www.xarg.org/2014/03/rational-numbers-in-javascript/ * - * Copyright (c) 2015, Robert Eisele (robert@xarg.org) + * Copyright (c) 2021, Robert Eisele (robert@xarg.org) * Dual licensed under the MIT or GPL Version 2 licenses. **/ @@ -2489,7 +2489,7 @@ module.exports = function(m, k){ * * @constructor */ - function IntermediateInheritor() {} + function IntermediateInheritor() { } IntermediateInheritor.prototype = Error.prototype; errorConstructor.prototype = new IntermediateInheritor(); @@ -2511,6 +2511,32 @@ module.exports = function(m, k){ throw new InvalidParameter(); } + function factorize(num) { + + var factors = {}; + + var n = num; + var i = 2; + var s = 4; + + while (s <= n) { + + while (n % i === 0) { + n /= i; + factors[i] = (factors[i] || 0) + 1; + } + s += 1 + 2 * i++; + } + + if (n !== num) { + if (n > 1) + factors[n] = (factors[n] || 0) + 1; + } else { + factors[num] = (factors[num] || 0) + 1; + } + return factors; + } + var parse = function(p1, p2) { var n = 0, d = 1, s = 1; @@ -2532,139 +2558,139 @@ module.exports = function(m, k){ switch (typeof p1) { case "object": - { - if ("d" in p1 && "n" in p1) { - n = p1["n"]; - d = p1["d"]; - if ("s" in p1) - n *= p1["s"]; - } else if (0 in p1) { - n = p1[0]; - if (1 in p1) - d = p1[1]; - } else { - throwInvalidParam(); + { + if ("d" in p1 && "n" in p1) { + n = p1["n"]; + d = p1["d"]; + if ("s" in p1) + n *= p1["s"]; + } else if (0 in p1) { + n = p1[0]; + if (1 in p1) + d = p1[1]; + } else { + throwInvalidParam(); + } + s = n * d; + break; } - s = n * d; - break; - } case "number": - { - if (p1 < 0) { - s = p1; - p1 = -p1; - } - - if (p1 % 1 === 0) { - n = p1; - } else if (p1 > 0) { // check for != 0, scale would become NaN (log(0)), which converges really slow - - if (p1 >= 1) { - z = Math.pow(10, Math.floor(1 + Math.log(p1) / Math.LN10)); - p1 /= z; + { + if (p1 < 0) { + s = p1; + p1 = -p1; } - // Using Farey Sequences - // http://www.johndcook.com/blog/2010/10/20/best-rational-approximation/ + if (p1 % 1 === 0) { + n = p1; + } else if (p1 > 0) { // check for != 0, scale would become NaN (log(0)), which converges really slow - while (B <= N && D <= N) { - M = (A + C) / (B + D); + if (p1 >= 1) { + z = Math.pow(10, Math.floor(1 + Math.log(p1) / Math.LN10)); + p1 /= z; + } - if (p1 === M) { - if (B + D <= N) { - n = A + C; - d = B + D; - } else if (D > B) { - n = C; - d = D; - } else { - n = A; - d = B; - } - break; + // Using Farey Sequences + // http://www.johndcook.com/blog/2010/10/20/best-rational-approximation/ - } else { + while (B <= N && D <= N) { + M = (A + C) / (B + D); - if (p1 > M) { - A += C; - B += D; - } else { - C += A; - D += B; - } + if (p1 === M) { + if (B + D <= N) { + n = A + C; + d = B + D; + } else if (D > B) { + n = C; + d = D; + } else { + n = A; + d = B; + } + break; - if (B > N) { - n = C; - d = D; } else { - n = A; - d = B; + + if (p1 > M) { + A += C; + B += D; + } else { + C += A; + D += B; + } + + if (B > N) { + n = C; + d = D; + } else { + n = A; + d = B; + } } } + n *= z; + } else if (isNaN(p1) || isNaN(p2)) { + d = n = NaN; } - n *= z; - } else if (isNaN(p1) || isNaN(p2)) { - d = n = NaN; + break; } - break; - } case "string": - { - B = p1.match(/\d+|./g); - - if (B === null) - throwInvalidParam(); + { + B = p1.match(/\d+|./g); - if (B[A] === '-') {// Check for minus sign at the beginning - s = -1; - A++; - } else if (B[A] === '+') {// Check for plus sign at the beginning - A++; - } + if (B === null) + throwInvalidParam(); - if (B.length === A + 1) { // Check if it's just a simple number "1234" - w = assign(B[A++], s); - } else if (B[A + 1] === '.' || B[A] === '.') { // Check if it's a decimal number - - if (B[A] !== '.') { // Handle 0.5 and .5 - v = assign(B[A++], s); + if (B[A] === '-') {// Check for minus sign at the beginning + s = -1; + A++; + } else if (B[A] === '+') {// Check for plus sign at the beginning + A++; } - A++; - // Check for decimal places - if (A + 1 === B.length || B[A + 1] === '(' && B[A + 3] === ')' || B[A + 1] === "'" && B[A + 3] === "'") { - w = assign(B[A], s); - y = Math.pow(10, B[A].length); + if (B.length === A + 1) { // Check if it's just a simple number "1234" + w = assign(B[A++], s); + } else if (B[A + 1] === '.' || B[A] === '.') { // Check if it's a decimal number + + if (B[A] !== '.') { // Handle 0.5 and .5 + v = assign(B[A++], s); + } A++; - } - // Check for repeating places - if (B[A] === '(' && B[A + 2] === ')' || B[A] === "'" && B[A + 2] === "'") { - x = assign(B[A + 1], s); - z = Math.pow(10, B[A + 1].length) - 1; + // Check for decimal places + if (A + 1 === B.length || B[A + 1] === '(' && B[A + 3] === ')' || B[A + 1] === "'" && B[A + 3] === "'") { + w = assign(B[A], s); + y = Math.pow(10, B[A].length); + A++; + } + + // Check for repeating places + if (B[A] === '(' && B[A + 2] === ')' || B[A] === "'" && B[A + 2] === "'") { + x = assign(B[A + 1], s); + z = Math.pow(10, B[A + 1].length) - 1; + A += 3; + } + + } else if (B[A + 1] === '/' || B[A + 1] === ':') { // Check for a simple fraction "123/456" or "123:456" + w = assign(B[A], s); + y = assign(B[A + 2], 1); A += 3; + } else if (B[A + 3] === '/' && B[A + 1] === ' ') { // Check for a complex fraction "123 1/2" + v = assign(B[A], s); + w = assign(B[A + 2], s); + y = assign(B[A + 4], 1); + A += 5; } - } else if (B[A + 1] === '/' || B[A + 1] === ':') { // Check for a simple fraction "123/456" or "123:456" - w = assign(B[A], s); - y = assign(B[A + 2], 1); - A += 3; - } else if (B[A + 3] === '/' && B[A + 1] === ' ') { // Check for a complex fraction "123 1/2" - v = assign(B[A], s); - w = assign(B[A + 2], s); - y = assign(B[A + 4], 1); - A += 5; - } + if (B.length <= A) { // Check for more tokens on the stack + d = y * z; + s = /* void */ + n = x + d * v + z * w; + break; + } - if (B.length <= A) { // Check for more tokens on the stack - d = y * z; - s = /* void */ - n = x + d * v + z * w; - break; + /* Fall through on error */ } - - /* Fall through on error */ - } default: throwInvalidParam(); } @@ -2694,11 +2720,11 @@ module.exports = function(m, k){ function cycleLen(n, d) { for (; d % 2 === 0; - d /= 2) { + d /= 2) { } for (; d % 5 === 0; - d /= 5) { + d /= 5) { } if (d === 1) // Catch non-cyclic numbers @@ -2722,7 +2748,7 @@ module.exports = function(m, k){ } - function cycleStart(n, d, len) { + function cycleStart(n, d, len) { var rem1 = 1; var rem2 = modpow(10, len, d); @@ -2771,23 +2797,13 @@ module.exports = function(m, k){ parse(a, b); - if (Fraction['REDUCE']) { - a = gcd(P["d"], P["n"]); // Abuse a - } else { - a = 1; - } + a = gcd(P["d"], P["n"]); // Abuse variable a this["s"] = P["s"]; this["n"] = P["n"] / a; this["d"] = P["d"] / a; } - /** - * Boolean global variable to be able to disable automatic reduction of the fraction - * - */ - Fraction['REDUCE'] = 1; - Fraction.prototype = { "s": 1, @@ -2823,9 +2839,9 @@ module.exports = function(m, k){ parse(a, b); return new Fraction( - this["s"] * this["n"] * P["d"] + P["s"] * this["d"] * P["n"], - this["d"] * P["d"] - ); + this["s"] * this["n"] * P["d"] + P["s"] * this["d"] * P["n"], + this["d"] * P["d"] + ); }, /** @@ -2837,9 +2853,9 @@ module.exports = function(m, k){ parse(a, b); return new Fraction( - this["s"] * this["n"] * P["d"] - P["s"] * this["d"] * P["n"], - this["d"] * P["d"] - ); + this["s"] * this["n"] * P["d"] - P["s"] * this["d"] * P["n"], + this["d"] * P["d"] + ); }, /** @@ -2851,9 +2867,9 @@ module.exports = function(m, k){ parse(a, b); return new Fraction( - this["s"] * P["s"] * this["n"] * P["n"], - this["d"] * P["d"] - ); + this["s"] * P["s"] * this["n"] * P["n"], + this["d"] * P["d"] + ); }, /** @@ -2865,9 +2881,9 @@ module.exports = function(m, k){ parse(a, b); return new Fraction( - this["s"] * P["s"] * this["n"] * P["d"], - this["d"] * P["n"] - ); + this["s"] * P["s"] * this["n"] * P["d"], + this["d"] * P["n"] + ); }, /** @@ -2914,9 +2930,9 @@ module.exports = function(m, k){ * => (b2 * a1 % a2 * b1) / (b1 * b2) */ return new Fraction( - this["s"] * (P["d"] * this["n"]) % (P["n"] * this["d"]), - P["d"] * this["d"] - ); + this["s"] * (P["d"] * this["n"]) % (P["n"] * this["d"]), + P["d"] * this["d"] + ); }, /** @@ -2996,7 +3012,7 @@ module.exports = function(m, k){ }, /** - * Gets the inverse of the fraction, means numerator and denumerator are exchanged + * Gets the inverse of the fraction, means numerator and denominator are exchanged * * Ex: new Fraction([-3, 4]).inverse() => -4 / 3 **/ @@ -3006,17 +3022,68 @@ module.exports = function(m, k){ }, /** - * Calculates the fraction to some integer exponent + * Calculates the fraction to some rational exponent, if possible * * Ex: new Fraction(-1,2).pow(-3) => -8 */ - "pow": function(m) { + "pow": function(a, b) { - if (m < 0) { - return new Fraction(Math.pow(this['s'] * this["d"], -m), Math.pow(this["n"], -m)); - } else { - return new Fraction(Math.pow(this['s'] * this["n"], m), Math.pow(this["d"], m)); + parse(a, b); + + // Trivial case when exp is an integer + + if (P['d'] === 1) { + + if (P['s'] < 0) { + return new Fraction(Math.pow(this['s'] * this["d"], P['n']), Math.pow(this["n"], P['n'])); + } else { + return new Fraction(Math.pow(this['s'] * this["n"], P['n']), Math.pow(this["d"], P['n'])); + } } + + // Negative roots become complex + // (-a/b)^(c/d) = x + // <=> (-1)^(c/d) * (a/b)^(c/d) = x + // <=> (cos(pi) + i*sin(pi))^(c/d) * (a/b)^(c/d) = x # rotate 1 by 180° + // <=> (cos(c*pi/d) + i*sin(c*pi/d)) * (a/b)^(c/d) = x # DeMoivre's formula in Q ( https://proofwiki.org/wiki/De_Moivre%27s_Formula/Rational_Index ) + // From which follows that only for c=0 the root is non-complex. c/d is a reduced fraction, so that sin(c/dpi)=0 occurs for d=1, which is handled by our trivial case. + if (this['s'] < 0) return null; + + // Now prime factor n and d + var N = factorize(this['n']); + var D = factorize(this['d']); + + // Exponentiate and take root for n and d individually + var n = 1; + var d = 1; + for (var k in N) { + if (k === '1') continue; + if (k === '0') { + n = 0; + break; + } + N[k]*= P['n']; + + if (N[k] % P['d'] === 0) { + N[k]/= P['d']; + } else return null; + n*= Math.pow(k, N[k]); + } + + for (var k in D) { + if (k === '1') continue; + D[k]*= P['n']; + + if (D[k] % P['d'] === 0) { + D[k]/= P['d']; + } else return null; + d*= Math.pow(k, D[k]); + } + + if (P['s'] < 0) { + return new Fraction(d, n); + } + return new Fraction(n, d); }, /** @@ -3165,7 +3232,7 @@ module.exports = function(m, k){ var b = this['d']; var res = []; - if (isNaN(this['n']) || isNaN(this['d'])) { + if (isNaN(a) || isNaN(b)) { return res; } @@ -3194,13 +3261,7 @@ module.exports = function(m, k){ return "NaN"; } - if (!Fraction['REDUCE']) { - g = gcd(N, D); - N /= g; - D /= g; - } - - dec = dec || 15; // 15 = decimal places when no repitation + dec = dec || 15; // 15 = decimal places when no repetation var cycLen = cycleLen(N, D); // Cycle length var cycOff = cycleStart(N, D, cycLen); // Cycle start @@ -3217,20 +3278,20 @@ module.exports = function(m, k){ if (cycLen) { - for (var i = cycOff; i--; ) { + for (var i = cycOff; i--;) { str += N / D | 0; N %= D; N *= 10; } str += "("; - for (var i = cycLen; i--; ) { + for (var i = cycLen; i--;) { str += N / D | 0; N %= D; N *= 10; } str += ")"; } else { - for (var i = dec; N && i--; ) { + for (var i = dec; N && i--;) { str += N / D | 0; N %= D; N *= 10; @@ -3245,7 +3306,7 @@ module.exports = function(m, k){ return Fraction; }); } else if (typeof exports === "object") { - Object.defineProperty(exports, "__esModule", {'value': true}); + Object.defineProperty(Fraction, "__esModule", { 'value': true }); Fraction['default'] = Fraction; Fraction['Fraction'] = Fraction; module['exports'] = Fraction; @@ -3256,31 +3317,6 @@ module.exports = function(m, k){ })(this); },{}],5:[function(require,module,exports){ -if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }; -} else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor - } -} - -},{}],6:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -3466,7 +3502,7 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],7:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ // A library of seedable RNGs implemented in Javascript. // // Usage: @@ -3528,7 +3564,7 @@ sr.tychei = tychei; module.exports = sr; -},{"./lib/alea":8,"./lib/tychei":9,"./lib/xor128":10,"./lib/xor4096":11,"./lib/xorshift7":12,"./lib/xorwow":13,"./seedrandom":14}],8:[function(require,module,exports){ +},{"./lib/alea":7,"./lib/tychei":8,"./lib/xor128":9,"./lib/xor4096":10,"./lib/xorshift7":11,"./lib/xorwow":12,"./seedrandom":13}],7:[function(require,module,exports){ // A port of an algorithm by Johannes Baagøe , 2010 // http://baagoe.com/en/RandomMusings/javascript/ // https://github.com/nquinlan/better-random-numbers-for-javascript-mirror @@ -3644,7 +3680,7 @@ if (module && module.exports) { -},{}],9:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ // A Javascript implementaion of the "Tyche-i" prng algorithm by // Samuel Neves and Filipe Araujo. // See https://eden.dei.uc.pt/~sneves/pubs/2011-snfa2.pdf @@ -3749,7 +3785,7 @@ if (module && module.exports) { -},{}],10:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ // A Javascript implementaion of the "xor128" prng algorithm by // George Marsaglia. See http://www.jstatsoft.org/v08/i14/paper @@ -3832,7 +3868,7 @@ if (module && module.exports) { -},{}],11:[function(require,module,exports){ +},{}],10:[function(require,module,exports){ // A Javascript implementaion of Richard Brent's Xorgens xor4096 algorithm. // // This fast non-cryptographic random number generator is designed for @@ -3980,7 +4016,7 @@ if (module && module.exports) { (typeof define) == 'function' && define // present with an AMD loader ); -},{}],12:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ // A Javascript implementaion of the "xorshift7" algorithm by // François Panneton and Pierre L'ecuyer: // "On the Xorgshift Random Number Generators" @@ -4079,7 +4115,7 @@ if (module && module.exports) { ); -},{}],13:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ // A Javascript implementaion of the "xorwow" prng algorithm by // George Marsaglia. See http://www.jstatsoft.org/v08/i14/paper @@ -4167,7 +4203,7 @@ if (module && module.exports) { -},{}],14:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ /* Copyright 2019 David Bau. @@ -4192,15 +4228,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -(function (pool, math) { +(function (global, pool, math) { // // The following constants are related to IEEE 754 limits. // -// Detect the global object, even if operating in strict mode. -// http://stackoverflow.com/a/14387057/265298 -var global = (0, eval)('this'), - width = 256, // each RC4 output is 0 <= x < 256 +var width = 256, // each RC4 output is 0 <= x < 256 chunks = 6, // at least six RC4 outputs for each double digits = 52, // there are 52 significant digits in a double rngname = 'random', // rngname: name for Math.random and Math.seedrandom @@ -4418,11 +4451,39 @@ if ((typeof module) == 'object' && module.exports) { // End anonymous scope, and pass initial values. })( + // global: `self` in browsers (including strict mode and web workers), + // otherwise `this` in Node and other environments + (typeof self !== 'undefined') ? self : this, [], // pool: entropy pool starts empty Math // math: package containing random, pow, and seedrandom ); -},{"crypto":3}],15:[function(require,module,exports){ +},{"crypto":3}],14:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],15:[function(require,module,exports){ module.exports = function isBuffer(arg) { return arg && typeof arg === 'object' && typeof arg.copy === 'function' @@ -4430,7 +4491,7 @@ module.exports = function isBuffer(arg) { && typeof arg.readUInt8 === 'function'; } },{}],16:[function(require,module,exports){ -(function (process,global){ +(function (process,global){(function (){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -5018,8 +5079,8 @@ function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./support/isBuffer":15,"_process":6,"inherits":5}],17:[function(require,module,exports){ +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":15,"_process":5,"inherits":14}],17:[function(require,module,exports){ const parse = require('../dist/tidal.js').parse const query = require('./queryArc.js' ).queryArc const Fraction = require( 'fraction.js' ) @@ -5091,6 +5152,8 @@ const rnd = function( phase ) { return new srand( phase.toFraction() )() } +let queryStart; + /* queryArc * * Generates events for provided pattern, starting at @@ -5104,6 +5167,7 @@ const queryArc = function( pattern, phase, duration ) { end = start.add( duration ), // get phase offset if scheduling begins in middle of event arc adjustedPhase = adjustPhase( phase, getPhaseIncr( pattern ), end ) + queryStart = phase.clone(); let eventList @@ -5416,7 +5480,7 @@ const handlers = { // initialize, then increment. this assumes that the pattern will be parsed once, // and then the resulting data structure will be queried repeatedly, enabling the use // of state. - group.count = group.count === undefined ? 0 : group.count + 1 + group.count = group.count === undefined ? queryStart.valueOf() : group.count + 1 const subpattern = group.values[ group.count % group.values.length ] const dur = duration.valueOf() <= 1 ? Fraction(1) : duration @@ -5667,5 +5731,5 @@ const handlers = { module.exports.queryArc = queryArc -},{"bjork":2,"fraction.js":4,"seedrandom":7,"util":16}]},{},[17])(17) +},{"bjork":2,"fraction.js":4,"seedrandom":6,"util":16}]},{},[17])(17) }); diff --git a/src/queryArc.js b/src/queryArc.js index e015401..0db1924 100644 --- a/src/queryArc.js +++ b/src/queryArc.js @@ -9,6 +9,8 @@ const rnd = function( phase ) { return new srand( phase.toFraction() )() } +let queryStart; + /* queryArc * * Generates events for provided pattern, starting at @@ -22,6 +24,7 @@ const queryArc = function( pattern, phase, duration ) { end = start.add( duration ), // get phase offset if scheduling begins in middle of event arc adjustedPhase = adjustPhase( phase, getPhaseIncr( pattern ), end ) + queryStart = phase.clone(); let eventList @@ -334,7 +337,7 @@ const handlers = { // initialize, then increment. this assumes that the pattern will be parsed once, // and then the resulting data structure will be queried repeatedly, enabling the use // of state. - group.count = group.count === undefined ? 0 : group.count + 1 + group.count = group.count === undefined ? queryStart.valueOf() : group.count + 1 const subpattern = group.values[ group.count % group.values.length ] const dur = duration.valueOf() <= 1 ? Fraction(1) : duration diff --git a/test/test-pattern.js b/test/test-pattern.js new file mode 100644 index 0000000..57dc57b --- /dev/null +++ b/test/test-pattern.js @@ -0,0 +1,27 @@ +/* test-pattern.js + * + * A test for the Pattern function + * + */ + +const assert = require('assert'); +const Pattern = require('../dist/pattern.js'); +const Fraction = require('fraction.js'); +const util = require('util'); + +describe('Testing Pattern.', () => { + it('should query events', () => { + const result = Pattern('[A ]') + .query(0, 1) + .map((e) => e.value); + + assert.deepEqual(result, ['A', 'B']); + }); + it('should query events from a non zero start', () => { + const result = Pattern('[A ]') + .query(1, 1) + .map((e) => e.value); + + assert.deepEqual(result, ['A', 'C']); + }); +});