Skip to content

Instantly share code, notes, and snippets.

@mattparker
Created August 30, 2012 12:43
Show Gist options
  • Save mattparker/3527740 to your computer and use it in GitHub Desktop.
Save mattparker/3527740 to your computer and use it in GitHub Desktop.
YUI Y.Array set methods (intersect, diff, union)
YUI.add("arraySet", function (Y) {
"use strict";
var A = Y.Array,
L = Y.Lang,
iO = A.indexOf;
/**
* Intersection of two or more arrays.
*
* @method intersect
* @param {Array} First array (or array-like thing - is passed through Y.Array() first.
* @param {Array} Second array (or array-like thing - is passed through Y.Array() first.
* @param {Function} Comparison function (optional). If not passed,
* will use indexOf tests (which should do strict === comparison). If
* a function is passed, it will be used to compare the items in the
* arrays. This function will receive two arguments: the first
* will be an item from the first array, the second will be an item
* from the second array. This might be useful for arrays of objects
* for example. Note that the return value may contain duplicate values - you
* will need to use Y.Array.unique(returnedArray, comparisonFunction). This
* is because there's no prior reason that the comparison function used
* to determine the intersection should also be used as the equality function
* when determining uniqueness of values (although you're into the edge cases
* where they're not).
*
* @return {Array} Array of items that appear in both original arrays.
*/
A.intersect = function (arr1, arr2, comparison) {
var i = 0,
args,
a1,
a2,
len1 = 0,
ret = [],
fn;
if (comparison && L.isFunction(comparison)) {
fn = function (item) {
var i;
for (i = 0; i < a2.length; i = i + 1) {
if (comparison(item, a2[i])) {
ret.push(item, a2[i]);
}
}
};
} else {
fn = function (item) {
if (iO(a2, item) !== -1) {
ret.push(item);
}
};
}
a1 = new A(arr1);
a2 = new A(arr2);
len1 = a1.length;
args = new A(arguments);
// we've got more than two arrays:
if ((args.length === 3 && !L.isFunction(comparison)) ||
(args.length > 3)) {
// intersection of the first two:
if (L.isFunction(args[args.length - 1])) {
ret = A.intersect(a1, a2, args[args.length - 1]);
} else {
ret = A.intersect(a1, a2);
}
// replace the first two with their intersection
args.splice(0, 2, ret);
// rinse and repeat
return A.intersect.apply(this, args);
}
for (i = 0; i < len1; i = i + 1) {
fn(a1[i]);
}
return ret;
};
/**
* Returns union of two or more arrays, with duplicates removed
*
* @method union
* @param {Array}
* @param {Array}
* ... (you can pass as many arrays as you like. Each argument
* will be passed through Y.Array so conversions will happen
* if possible; if not will be skipped.
* @param {Function} Comparison function to be used in call to unique (optional)
* @return {Array}
*/
A.union = function (arr1, arr2, comparison) {
var args = new A(arguments),
a1 = new A(arr1),
a2 = new A(arr2),
a3;
// more than 2: do the first two and then recurse.
if (args.length > 2 && !L.isFunction(comparison)) {
a3 = A.union(arr1, arr2);
args.splice(0, 2, a3);
return A.union.apply(this, args);
}
// set up arguments for call to apply:
a2.unshift(a1.length, 0);
a1.splice.apply(a1, a2);
return A.unique(a1, comparison);
};
/**
* Calculates the difference between two arrays: that is,
* the values in arr1 that are not contained in arr2.
*
* @method diff
* @param {Array} arr1 Or array-like something - passed through Y.Array first
* @param {Array} arr2 Or array-like something - passed through Y.Array first
* @param {Function} comparison To tell if two items are equal.
* The function will receive two arguments: the first is the value from
* the first array, the second is a value from the second array to compare to.
* The comparison function is optional. If the function returns true
* they are the same.
* @return {Array}
*/
A.diff = function (arr1, arr2, comparison) {
var i = 0,
ret = [],
a1,
a2,
cb,
fn = (comparison && L.isFunction(comparison)) ?
comparison :
false;
a1 = new A(arr1);
a2 = new A(arr2);
if (fn) {
cb = function (a) { return fn(a1[i], a); };
for (i = 0; i < a1.length; i = i + 1) {
if (A.find(a2, cb) === null) {
ret.push(a1[i]);
}
}
} else {
for (i = 0; i < a1.length; i = i + 1) {
if (iO(a2, a1[i]) === -1) {
ret.push(a1[i]);
}
}
}
return ret;
};
}, "0.1", {requires: ["array"]});
@mattparker
Copy link
Author

Here are the tests for the above. It also passes jslint and has had some thought about performance given to it (though not claiming perfection!).

YUI.add("arraySet-tests", function (Y) {

    var ASSERT = Y.Assert,
        ARRAYASSERT = Y.ArrayAssert;

    var testSimpleIntersects = new Y.Test.Case({

        name: "Test of simple arrays of numbers, strings",

        testNumbers: function () {
            var a1 = [1,2,3,4,5],
                a2 = [3,4,5,6],
                answer = Y.Array.intersect(a1, a2);

            ARRAYASSERT.itemsAreSame([3,4,5], answer, "Intersection of arrays of numbers.");

        },

        testStrings: function () {
            var a1 = ["a", "b", "c", "d"],
                a2 = ["a", "e", "c"],
                answer = Y.Array.intersect(a1, a2);

            ARRAYASSERT.itemsAreSame(["a", "c"], answer, "Intersection of arrays of strings.");

        },            

        testMixed: function () {
            var a1 = [1, "a", {}, "b", 2, "c", "d"],
                a2 = [2, "d", {}],
                answer = Y.Array.intersect(a1, a2);

            ARRAYASSERT.itemsAreSame([2, "d"], answer, "Intersection of arrays of strings.");

        },

        testNoIntersection: function () {
            var a1 = ["a", 1, 2, 3, "b", "c", "d"],
                a2 = [4, 5, "e", "f"],
                answer = Y.Array.intersect(a1, a2);

            ARRAYASSERT.itemsAreSame([], answer, "Intersection of arrays of strings.");

        },

        testError: function () {
            var a1 = "bob",
                a2 = function() {};
                answer = Y.Array.intersect(a1, a2);

            ASSERT.areEqual(false, answer, "Return false if not arrays passed.");

        }



    });








    var testMoreThanTwoArraysIntersects = new Y.Test.Case({

        name: "Test of more than two arrays of numbers, strings",

        testNumbers: function () {
            var a1 = [1,2,3,4,5],
                a2 = [3,4,5,6],
                a3 = [3, 5];
                answer = Y.Array.intersect(a1, a2, a3);

            ARRAYASSERT.itemsAreSame([3,5], answer, "Intersection of 3 arrays of numbers.");

        },

        testStrings: function () {
            var a1 = ["a", "b", "c"],
                a2 = ["d", "c"],
                a3 = ["b", "c", "d"],
                answer = Y.Array.intersect(a1, a2, a3); 
            ARRAYASSERT.itemsAreSame(["c"], answer, "Intersections of 3 arrays of strings");
        },

        testNoIntersection: function () {
            var a1 = ["a", "b", "c"],
                a2 = [1, 2],
                a3 = [5, "d"],
                answer = Y.Array.intersect(a1, a2, a3);
            ARRAYASSERT.itemsAreSame([], answer, "Intersections of 3 arrays of strings");
        },

        testIntersectionOfEmptyArray: function () {
            var a1 = ["a", "b", "c"],
                a2 = [],
                a3 = ["b", "c", "d"],
                answer = Y.Array.intersect(a1, a2, a3);
            ARRAYASSERT.itemsAreSame([], answer, "Intersections of an empty arrays is empty");
        }

    });







    var testIntersectsWithCustomComparison = new Y.Test.Case({

        name: "Test of two arrays with custom comparisons",

        testNumbersNear: function () {
            var a1 = [1, 7, 13],
                a2 = [2, 3, 13, 14],
                com = function (a,b) { return ((a-b) < 2 && (a-b) > -2)},
                answer = Y.Array.intersect(a1, a2, com);

            // manually uniqueness using a === b:
            answer = Y.Array.unique(answer);
            ARRAYASSERT.itemsAreSame([1, 2, 13, 14], answer, "Intersection of 2 arrays of numbers that are close.");

        },

        testDates: function () {
            var a1 = [new Date("01/04/2012"), new Date("25/08/2012")],
                a2 = [new Date("01/04/2012"), new Date("02/04/2012")],//"2012-04-02")],
                com = function (a,b) { 
                    return (a.getMonth() == b.getMonth() && a.getYear() == b.getYear() && a.getDay() == b.getDay());
                },
                answer = Y.Array.intersect(a1, a2, com),
                ansStr,
                expected = new Date("01/04/2012");


            answer = Y.Array.unique(answer, com);
            ansStr = answer[0];

            ARRAYASSERT.itemsAreSame(expected.toString(), ansStr.toString(), "Intersection of 2 arrays of date objects.");
            ASSERT.areEqual(1, answer.length, "One value in the answer");

        },

        testObjectPropertyWithMoreThanTwoArrays: function () {
            var a1 = [{p: 2}, {p: 3}, {p: 5}],
                a2 = [{p: 3}, {p: 10}, {p: 14}],
                a3 = [{p: 10}, {p: 3}, {p: 5}, {p: 14}],
                com = function (a, b) { return a.p === b.p;},
                answer = Y.Array.intersect(a1, a2, a3, com);

            // manually unique-ify the response
            answer = Y.Array.unique(answer, com);
            ARRAYASSERT.itemsAreSame(3, answer[0].p, "Intersection of 3 arrays of objects with custom comparison");           
            ASSERT.areEqual(1, answer.length, "One value in the answer");


        },

        testObjectPropertyWithMoreThanTwoArraysOrderDoesntMatter: function () {
            var a3 = [{p: 2}, {p: 3}, {p: 5}],
                a1 = [{p: 3}, {p: 10}, {p: 14}],
                a2 = [{p: 10}, {p: 3}, {p: 5}, {p: 14}],
                com = function (a, b) { return a.p === b.p;},
                answer = Y.Array.intersect(a1, a2, a3, com);

            answer = Y.Array.unique(answer, com);
            ARRAYASSERT.itemsAreSame(3, answer[0].p, "Intersection of 3 arrays of objects with custom comparison - order of args doesnt matter");            
            ASSERT.areEqual(1, answer.length, "One value in the answer");


        }

    });


    var suite = new Y.Test.Suite("ArraySet.intersect");
    suite.add(testSimpleIntersects);
    suite.add(testMoreThanTwoArraysIntersects);
    suite.add(testIntersectsWithCustomComparison);    
    Y.Test.Runner.add(suite);











    var testUnions = new Y.Test.Case({
        name : "Test unions of arrays",

        "union of 2 numerical arrays with no duplicates should return one array" : function () {
            var a1 = [1,2,3],
                a2 = [4,5,6],
                answer = Y.Array.union(a1, a2);

            ARRAYASSERT.itemsAreSame([1,2,3,4,5,6], answer, "Straightforward union of distinct numbers");

        },


        "union of 2 numerical arrays should remove duplicates" : function () {
            var a1 = [1,4,2,3],
                a2 = [2,4,5,6],
                answer = Y.Array.union(a1, a2);

            ARRAYASSERT.itemsAreSame([1,4,2,3,5,6], answer, "Straightforward union of distinct numbers with duplicates removed");
        },


        "union of 2 arrays should remove duplicates with strict equality": function () {

            var a1 = [1,2,3, undefined, false],
                a2 = ["1", "2", "3", null, true, 0],
                answer = Y.Array.union(a1, a2);

            ARRAYASSERT.itemsAreSame([1,2,3, undefined, false, "1", "2", "3", null, true, 0], answer, "Test strict equality in removing duplicates");

        },


        "simple union of 3 arrays should return one array": function () {
            var a1 = [1,2,3],
                a2 = [4,5,6],
                a3 = [7,8,9],
                answer = Y.Array.union(a1, a2, a3);

            ARRAYASSERT.itemsAreSame([1,2,3, 4,5,6,7,8,9], answer);
        },


        "simple union of 3 arrays should remove duplicates": function () {
            var a1 = [1,2,3],
                a2 = [2,5,6],
                a3 = [7,1,9],
                answer = Y.Array.union(a1, a2, a3);

            ARRAYASSERT.itemsAreSame([1,2,3,5,6,7,9], answer);
        },

        "simple union of objects should not remove duplicates": function () {
            var a1 = [{a:2}],
                a2 = [{a:2}],
                a3 = [{a:3}],
                answer = Y.Array.union(a1, a2, a3);

            ASSERT.areEqual(2, answer[0].a);
            ASSERT.areEqual(2, answer[1].a);
            ASSERT.areEqual(3, answer[2].a);
            ASSERT.areEqual(3, answer.length);
        },

        "union of objects with comparison function should remove duplicates": function () {
            var a1 = [{a:2}],
                a2 = [{a:2}],
                a3 = [{a:3}],
                f = function (x, y) { return (x.a === y.a);},
                answer = Y.Array.union(a1, a2, a3, f);

            ASSERT.areEqual(2, answer[0].a);
            ASSERT.areEqual(3, answer[1].a);
            ASSERT.areEqual(2, answer.length);
        }




    });

    var suite2 = new Y.Test.Suite("ArraySet.union");
    suite2.add(testUnions);
    Y.Test.Runner.add(suite2);











    var testDiff = new Y.Test.Case({
        name : "Test diff of arrays",

        "diff of two different numerical arrays should not remove any" : function () {
            var a1 = [1,2,3],
                a2 = [4,5,6],
                answer = Y.Array.diff(a1, a2);

            ARRAYASSERT.itemsAreSame([1,2,3], answer);

        },


        "diff of two identical numerical arrays should return empty array" : function () {
            var a1 = [1,2,3],
                a2 = [1,2,3],
                answer = Y.Array.diff(a1, a2);

            ARRAYASSERT.itemsAreSame([], answer);

        },   

        "diff of arrays of objects should not remove any": function () {
            var a1 = [{a:1}],
                a2 = [{a:1}],
                answer = Y.Array.diff(a1, a2);

           ASSERT.areEqual(1, answer[0].a, answer);
           ASSERT.areEqual(1, answer.length, answer);
        },

        "diff of arrays of objects with comparison function should remove some" : function () {
            var a1 = [{a:1}],
                a2 = [{a:1}],
                comp = function (x,y) { return (x.a === y.a);},
                answer = Y.Array.diff(a1, a2, comp);

           ASSERT.areEqual(0, answer.length);
        },

        "diff of mixed values with crazy comparison function should remove some" : function () {
            var a1 = [1, "abc", {a:5}, function () {return 2}, true, undefined],
                a2 = [{a:2}, "ab", 1],
                comp = function (x,y) {
                    var c,d, L = Y.Lang;
                    if (L.isFunction(x)) {
                        c = x();
                    } else if (L.isObject(x)) {
                        c = x.a;
                    } else {
                        c = x;
                    }
                    if (L.isFunction(y)) {
                        d = y();
                    } else if (L.isObject(y)) {
                        d = y.a;
                    } else {
                        d = y;
                    }
                    // note weak comparison
                    ret = (c == d);

                    return ret;

                },
                answer = Y.Array.diff(a1, a2, comp);
            // should be:
            // ["abc", {a:5}, undefined] 

            ASSERT.areEqual(3, answer.length, "3 items left");
            ASSERT.areEqual("abc", answer[0]);
            ASSERT.areEqual(5, answer[1].a);
            ASSERT.areEqual(undefined, answer[2]);


        }

    });

    var suite3 = new Y.Test.Suite("ArraySet.diff");
    suite3.add(testDiff);
    Y.Test.Runner.add(suite3);








    Y.Test.Runner.run();
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment