Compare arrays as (multi-) sets

前端 未结 8 1701
梦如初夏
梦如初夏 2021-01-04 21:42

I\'m looking for an efficient way to find out whether two arrays contain same amounts of equal elements (in the == sense), in any order:

foo = {         


        
8条回答
  •  长发绾君心
    2021-01-04 22:02

    UPDATE

    As @Bergi and @thg435 point out my previous implementation was flawed so here is another implementation:

    function sameElements(a, b) {
        var objs = [];
        // if length is not the same then must not be equal
        if (a.length != b.length) return false;
    
        // do an initial sort which will group types
        a.sort();
        b.sort();
    
        for ( var i = 0; i < a.length; i++ ) {
    
            var aIsPrimitive = isPrimitive(a[i]);
            var bIsPrimitive = isPrimitive(b[i]);
    
            // NaN will not equal itself
            if( a[i] !== a[i] ) {
                if( b[i] === b[i] ) {
                    return false;
                }
            }
            else if (aIsPrimitive && bIsPrimitive) {
    
                if( a[i] != b[i] ) return false;
            }
            // if not primitive increment the __count property
            else if (!aIsPrimitive && !bIsPrimitive) {
                incrementCountA(a[i]);
                incrementCountB(b[i]);
                // keep track on non-primitive objects
                objs.push(i);
            }
            // if both types are not the same then this array
            // contains different number of primitives
            else {
                return false;
            }
    
        }
    
        var result = true;
    
        for (var i = 0; i < objs.length; i++) {
            var ind = objs[i];
            // if __aCount and __bCount match then object exists same
            // number of times in both arrays
            if( a[ind].__aCount !== a[ind].__bCount ) result = false;
            if( b[ind].__aCount !== b[ind].__bCount ) result = false;
    
            // revert object to what it was 
            // before entering this function
            delete a[ind].__aCount;
            delete a[ind].__bCount;
            delete b[ind].__aCount;
            delete b[ind].__bCount;
        }
    
        return result;
    }
    
    // inspired by @Bergi's code
    function isPrimitive(arg) {
        return Object(arg) !== arg;
    }
    
    function incrementCountA(arg) {
        if (arg.hasOwnProperty("__aCount")) {
            arg.__aCount = arg.__aCount + 1;
        } else {
            Object.defineProperty(arg, "__aCount", {
                enumerable: false,
                value: 1,
                writable: true,
                configurable: true
            });
        }
    }
    function incrementCountB(arg) {
        if (arg.hasOwnProperty("__bCount")) {
            arg.__bCount = arg.__bCount + 1;
        } else {
            Object.defineProperty(arg, "__bCount", {
                enumerable: false,
                value: 1,
                writable: true,
                configurable: true
            });
        }
    }
    

    Then just call the function

    sameElements( ["NaN"], [NaN] ); // false
    
    // As "1" == 1 returns true
    sameElements( [1],["1"] ); // true
    
    sameElements( [1,2], [1,2,3] ); //false
    

    The above implement actually defines a new property called "__count" that is used to keep track of non-primitive elements in both arrays. These are deleted before the function returns so as to leave the array elements as before.

    Fiddle here

    jsperf here.

    The reason I changed the jsperf test case was that as @Bergi states the test arrays, especially the fact there were only 2 unique objects in the whole array is not representative of what we are testing for.

    One other advantage of this implementation is that if you need to make it compatible with pre IE9 browsers instead of using the defineProperty to create a non-enumerable property you could just use a normal property.

提交回复
热议问题