[javascript] JavaScript에서 배열을 비교하는 방법은 무엇입니까?

두 배열을 이상적으로 효율적으로 비교하고 싶습니다. true그들이 동일하고 false그렇지 않다면 공상 이 아닙니다. 당연히 비교 연산자가 작동하지 않는 것 같습니다.

var a1 = [1,2,3];
var a2 = [1,2,3];
console.log(a1==a2);    // Returns false
console.log(JSON.stringify(a1)==JSON.stringify(a2));    // Returns true

각 배열의 JSON 인코딩은 각 값을 반복하지 않고 배열을 간단히 비교하는 더 빠르고 “더 나은”방법이 있습니까?



답변

배열을 비교하려면 배열을 반복하고 모든 값을 비교하십시오.

배열 비교 :

// Warn if overriding existing method
if(Array.prototype.equals)
    console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
// attach the .equals method to Array's prototype to call it on any array
Array.prototype.equals = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time 
    if (this.length != array.length)
        return false;

    for (var i = 0, l=this.length; i < l; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].equals(array[i]))
                return false;
        }
        else if (this[i] != array[i]) {
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;
        }
    }
    return true;
}
// Hide method from for-in loops
Object.defineProperty(Array.prototype, "equals", {enumerable: false});

용법:

[1, 2, [3, 4]].equals([1, 2, [3, 2]]) === false;
[1, "2,3"].equals([1, 2, 3]) === false;
[1, 2, [3, 4]].equals([1, 2, [3, 4]]) === true;
[1, 2, 1, 2].equals([1, 2, 1, 2]) === true;

하지만 문자열을 비교하는 것이 훨씬 빠릅니다. 루프가 없습니다 … ” 라고 말하면 루프 가 있다는 것을 알아야합니다. 첫 번째 재귀 루프는 Array를 문자열로 변환하고 두 번째는 두 문자열을 비교합니다. 이 방법은 은 string 사용보다 빠릅니다 .

더 많은 양의 데이터는 항상 객체가 아닌 배열에 저장해야한다고 생각합니다. 그러나 개체를 사용하면 부분적으로 비교할 수도 있습니다.
방법은 다음과 같습니다.

객체 비교 :

위에서 언급했듯이 두 개의 객체 인스턴스 는 현재 동일한 데이터를 포함하더라도 결코 같지 않습니다.

({a:1, foo:"bar", numberOfTheBeast: 666}) == ({a:1, foo:"bar", numberOfTheBeast: 666})  //false

예를 들어 객체 내에 개인 변수 가있을 수 있기 때문 입니다.

그러나 객체 구조를 사용하여 데이터를 포함하는 경우 비교는 여전히 가능합니다.

Object.prototype.equals = function(object2) {
    //For the first loop, we only check for types
    for (propName in this) {
        //Check for inherited methods and properties - like .equals itself
        //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
        //Return false if the return value is different
        if (this.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) {
            return false;
        }
        //Check instance type
        else if (typeof this[propName] != typeof object2[propName]) {
            //Different types => not equal
            return false;
        }
    }
    //Now a deeper check using other objects property names
    for(propName in object2) {
        //We must check instances anyway, there may be a property that only exists in object2
            //I wonder, if remembering the checked values from the first loop would be faster or not 
        if (this.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) {
            return false;
        }
        else if (typeof this[propName] != typeof object2[propName]) {
            return false;
        }
        //If the property is inherited, do not check any more (it must be equa if both objects inherit it)
        if(!this.hasOwnProperty(propName))
          continue;

        //Now the detail check and recursion

        //This returns the script back to the array comparing
        /**REQUIRES Array.equals**/
        if (this[propName] instanceof Array && object2[propName] instanceof Array) {
                   // recurse into the nested arrays
           if (!this[propName].equals(object2[propName]))
                        return false;
        }
        else if (this[propName] instanceof Object && object2[propName] instanceof Object) {
                   // recurse into another objects
                   //console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
           if (!this[propName].equals(object2[propName]))
                        return false;
        }
        //Normal value comparison for strings and numbers
        else if(this[propName] != object2[propName]) {
           return false;
        }
    }
    //If everything passed, let's say YES
    return true;
}  

그러나 이것은 클래스 인스턴스 및 기타 항목이 아닌 데이터와 같은 JSON을 비교하는 역할을한다는 것을 기억하십시오. mor 복잡한 객체를 비교하려면 이 답변과 superlong 기능을보십시오 .
이 작업을 수행하려면 Array.equals원래 기능을 약간 편집해야합니다.

...
    // Check if we have nested arrays
    if (this[i] instanceof Array && array[i] instanceof Array) {
        // recurse into the nested arrays
        if (!this[i].equals(array[i]))
            return false;
    }
    /**REQUIRES OBJECT COMPARE**/
    else if (this[i] instanceof Object && array[i] instanceof Object) {
        // recurse into another objects
        //console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
        if (!this[i].equals(array[i]))
            return false;
        }
    else if (this[i] != array[i]) {
...

나는 두 기능 모두에 대해 작은 테스트 도구를 .

보너스 : 중첩 배열 indexOfcontains

Samy Bencherif는 중첩 배열에서 특정 객체를 검색하는 경우 유용한 기능을 준비 했습니다. https://jsfiddle.net/SamyBencherif/8352y6yw/


답변

이것은 스칼라 배열에서만 작동하지만 (아래 참고 참조) 짧습니다.

array1.length === array2.length && array1.every(function(value, index) { return value === array2[index]})

RMA, ECMAScript 6 / CoffeeScript / 화살표 기능이있는 TypeScript에서 :

array1.length === array2.length && array1.every((value, index) => value === array2[index])

(참고 : 여기서 ‘scalar’는 ===숫자, 문자열, 참조 객체, 참조 함수를 사용하여 직접 비교할 수있는 값을 의미합니다 . 비교 연산자에 대한 자세한 내용 은 MDN 참조참조 하십시오 ).

최신 정보

주석에서 읽은 내용에서 배열을 정렬하고 비교하면 정확한 결과를 얻을 수 있습니다.

array1.length === array2.length && array1.sort().every(function(value, index) { return value === array2.sort()[index]});

예 :

array1 = [2,3,1,4];
array2 = [1,2,3,4];

그런 다음 위의 코드는 true


답변

Underscore 및 Lodash에서 배열 / 객체 무거운 코딩 프로젝트에 Underscore 라이브러리를 사용하고 싶습니다. 배열 또는 객체를 비교할 때 다음과 같습니다.

_.isEqual(array1, array2)   // returns a boolean
_.isEqual(object1, object2) // returns a boolean

답변

이것은 JSON stringify를 사용하여 수행하는 가장 간단한 방법이며 일부 상황에서 가장 좋은 솔루션 일 수 있습니다.

JSON.stringify(a1) === JSON.stringify(a2);

이렇게하면 객체 a1a2문자열 로 변환되어 비교할 수 있습니다. 위의 답변 중 하나에 표시된 정렬 알고리즘을 사용하여 객체를 정렬 할 수 있기 때문에 대부분의 경우 순서가 중요합니다.

더 이상 객체를 비교하지 않고 객체의 문자열 표현을 유의하십시오. 정확히 원하는 것이 아닐 수도 있습니다.


답변

“동일한”이라는 말의 의미가 확실하지 않습니다. 예를 들어, 배열 ab아래가 동일합니까 (중첩 배열에 유의)?

var a = ["foo", ["bar"]], b = ["foo", ["bar"]];

다음은 엄격한 등식을 사용하여 각 배열의 해당 요소를 차례로 비교하고 자체 배열 인 배열 요소의 재귀 비교를 수행하지 않는 최적화 된 배열 비교 함수입니다. 위의 예에서는 arraysIdentical(a, b)을 반환 false합니다. 일반적인 경우에는 작동하며 JSON 및 join()기반 솔루션은 다음을 수행하지 않습니다.

function arraysIdentical(a, b) {
    var i = a.length;
    if (i != b.length) return false;
    while (i--) {
        if (a[i] !== b[i]) return false;
    }
    return true;
};


답변

실용적인 방법

“올바르지 않은”솔루션과 달리 “올바른”( “올바른”) 경우 특정 구현이 “The Right Way ™”라고 말하는 것이 잘못이라고 생각합니다. Tomáš의 솔루션은 문자열 기반 배열 비교에 비해 명백히 개선 된 것이지만 이것이 객관적으로 “옳다”는 것을 의미하지는 않습니다. 무엇 권리 어쨌든은? 가장 빠릅니까? 가장 유연합니까? 이해하기가 가장 쉬운가요? 디버깅하는 것이 가장 빠릅니까? 가장 적은 작업을 사용합니까? 부작용이 있습니까? 어떤 솔루션도 모든 것을 최대한 활용할 수는 없습니다.

Tomáš는 그의 솔루션이 빠르다고 말할 수 있지만 필연적으로 복잡하다고 말합니다. 중첩 여부에 관계없이 모든 어레이에서 작동하는 올인원 솔루션이 되려고합니다. 실제로, 그것은 단지 배열 이상의 것을 입력으로 받아들이고 여전히 “유효한”대답을 시도합니다.


일반은 재사용 성을 제공합니다

내 대답은 문제에 다르게 접근합니다. arrayCompare배열을 통한 스테핑에만 관련된 일반적인 절차 부터 시작하겠습니다 . 여기에서 arrayEqualand arrayDeepEqual등 의 다른 기본 비교 함수를 작성합니다.

// arrayCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayCompare = f => ([x,...xs]) => ([y,...ys]) =>
  x === undefined && y === undefined
    ? true
    : Boolean (f (x) (y)) && arrayCompare (f) (xs) (ys)

내 의견으로는, 가장 좋은 종류의 코드는 주석이 필요하지 않으며 예외는 아닙니다. 거의 아무런 노력 없이이 절차의 동작을 이해할 수 있도록 여기에는 거의 일어나지 않습니다. 물론, ES6 구문 중 일부는 현재 여러분에게 이질적으로 보이지만 ES6이 비교적 새롭기 때문입니다.

형식에서 알 수 있듯이 arrayCompare비교 함수, f두 개의 입력 배열 xs및을 사용 ys합니다. 대부분의 f (x) (y)경우 입력 배열의 각 요소를 호출 하기 만하면됩니다. 단락 평가 덕분에 false사용자가 정의한 f반품 일 경우 조기에 반품합니다 . 따라서, 이것은 비교기가 반복을 조기에 중단하고 불필요 할 때 나머지 입력 배열을 통한 루핑을 방지 할 수 있음을 의미합니다.false&&


엄격한 비교

다음으로 arrayCompare함수를 사용하여 필요한 다른 함수를 쉽게 만들 수 있습니다. 우리는 초등학교에서 시작합니다 arrayEqual

// equal :: a -> a -> Bool
const equal = x => y =>
  x === y // notice: triple equal

// arrayEqual :: [a] -> [a] -> Bool
const arrayEqual =
  arrayCompare (equal)

const xs = [1,2,3]
const ys = [1,2,3]
console.log (arrayEqual (xs) (ys))      //=> true
// (1 === 1) && (2 === 2) && (3 === 3)  //=> true

const zs = ['1','2','3']
console.log (arrayEqual (xs) (zs))      //=> false
// (1 === '1')                          //=> false

그렇게 간단합니다. (엄격한 평등을 위해) 를 사용 하는 것과 비교하는 비교기 함수 arrayEqual로 정의 할 수 있습니다 .arrayCompareab===

우리는 또한 equal자신의 기능으로 정의 합니다. 이것은 arrayCompare다른 데이터 유형 (배열)의 맥락에서 첫 번째 비교기를 활용하는 고차 함수 의 역할을 강조합니다 .


느슨한 비교

우리는 그냥 간단하게 정의 할 수 arrayLooseEqual사용하는 ==대신합니다. 이제 1(Number)를'1' (String) 하면 결과는 다음과 같습니다 true.

// looseEqual :: a -> a -> Bool
const looseEqual = x => y =>
  x == y // notice: double equal

// arrayLooseEqual :: [a] -> [a] -> Bool
const arrayLooseEqual =
  arrayCompare (looseEqual)

const xs = [1,2,3]
const ys = ['1','2','3']
console.log (arrayLooseEqual (xs) (ys))    //=> true
// (1 == '1') && (2 == '2') && (3 == '3')  //=> true

깊은 비교 (재귀)

아마도 이것은 단지 얕은 비교 방법이라는 것을 알았을 것입니다. Tomáš의 솔루션은 “The Right Way ™”입니다. 암묵적인 심층 비교를하기 때문입니다.

글쎄, 우리의 arrayCompare절차는 깊은 평등 테스트를 산들 바람으로 만드는 방법으로 사용할 수있을 정도로 다재다능합니다…

// isArray :: a -> Bool
const isArray =
  Array.isArray

// arrayDeepCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayDeepCompare = f =>
  arrayCompare (a => b =>
    isArray (a) && isArray (b)
      ? arrayDeepCompare (f) (a) (b)
      : f (a) (b))

const xs = [1,[2,[3]]]
const ys = [1,[2,['3']]]
console.log (arrayDeepCompare (equal) (xs) (ys)) //=> false
// (1 === 1) && (2 === 2) && (3 === '3')         //=> false

console.log (arrayDeepCompare (looseEqual) (xs) (ys)) //=> true
// (1 == 1) && (2 == 2) && (3 == '3')                 //=> true

그렇게 간단합니다. 우리는 또 다른 고차 함수를 사용하여 깊은 비교기를 만듭니다. 이번에 는 배열 과 배열 arrayCompare인지 확인하는 사용자 지정 비교기를 사용하여 줄 바꿈 합니다. 그렇다면 다른 방법으로 다시 비교 하고 사용자 지정 비교기 ( )와 비교 하십시오. 이를 통해 우리는 개별 요소를 실제로 비교하는 방법과 깊은 비교 동작을 유지할 수 있습니다. 즉, 위의 예에서 볼 수 있듯이abarrayDeepCompareabfequal , looseEqual또는 기타 비교기는 우리가합니다.

arrayDeepCompare카레 이기 때문에 이전 예제에서와 마찬가지로 부분적으로 적용 할 수 있습니다.

// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
  arrayDeepCompare (equal)

// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
  arrayDeepCompare (looseEqual)

필자는 필요에 따라 어레이에 대해 얕거나 깊은 비교를 명시 적으로 선택할 수 있기 때문에 Tomáš 솔루션보다 이미 분명히 개선되었습니다 .


객체 비교 (예)

이제 객체 배열이나 무언가가 있다면 어떨까요? 각 객체의 id값 이 동일한 경우 해당 배열을 “동일”한 것으로 간주하고 싶을 수도 있습니다 .

// idEqual :: {id: Number} -> {id: Number} -> Bool
const idEqual = x => y =>
  x.id !== undefined && x.id === y.id

// arrayIdEqual :: [a] -> [a] -> Bool
const arrayIdEqual =
  arrayCompare (idEqual)

const xs = [{id:1}, {id:2}]
const ys = [{id:1}, {id:2}]
console.log (arrayIdEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2)            //=> true

const zs = [{id:1}, {id:6}]
console.log (arrayIdEqual (xs) (zs)) //=> false
// (1 === 1) && (2 === 6)            //=> false

그렇게 간단합니다. 여기서는 바닐라 JS 객체를 사용했지만이 유형의 비교기는 모든 객체 유형에서 작동 할 수 있습니다 . 심지어 커스텀 객체까지. 이러한 종류의 동등성 테스트를 지원하기 위해 Tomáš의 솔루션을 완전히 재 작업해야합니다.

객체가있는 깊은 배열? 문제가 아니다. 우리는 매우 다양한 범용 기능을 구축하여 다양한 사용 사례에서 작동합니다.

const xs = [{id:1}, [{id:2}]]
const ys = [{id:1}, [{id:2}]]
console.log (arrayCompare (idEqual) (xs) (ys))     //=> false
console.log (arrayDeepCompare (idEqual) (xs) (ys)) //=> true

임의 비교 (예)

아니면 다른 종류의 완전히 임의적 인 비교를 원한다면 어떨까요? 어쩌면 나는 각각 x이 각각 보다 큰지 알고 싶습니다 y

// gt :: Number -> Number -> Bool
const gt = x => y =>
  x > y

// arrayGt :: [a] -> [a] -> Bool
const arrayGt = arrayCompare (gt)

const xs = [5,10,20]
const ys = [2,4,8]
console.log (arrayGt (xs) (ys))     //=> true
// (5 > 2) && (10 > 4) && (20 > 8)  //=> true

const zs = [6,12,24]
console.log (arrayGt (xs) (zs))     //=> false
// (5 > 6)                          //=> false

더 적은

더 적은 코드로 더 많은 일을하고 있음을 알 수 있습니다. arrayCompare자체적으로 복잡한 것은 없으며 우리가 만든 각 커스텀 비교기는 매우 간단한 구현을 가지고 있습니다.

쉽게, 우리는 두 배열을 비교하는 우리가 원하는 방법을 정확하게 정의 할 수 있습니다 – 얕은 깊이, 엄격한, 느슨한, 일부 객체 속성, 또는 임의의 계산, 또는 이들의 조합 – 모두가 하나 개의 절차를 사용하여 , arrayCompare. 어쩌면 RegExp비교기를 꿈꾸기도합니다 ! 아이들이 어떻게 정규 표현식을 좋아하는지 알고 있습니다.…

가장 빠릅니까? 아니. 그러나 아마도 둘 중 하나 일 필요는 없습니다. 속도가 코드의 품질을 측정하는 데 사용되는 유일한 지표 인 경우, 정말 많은 훌륭한 코드가 버려 질 것입니다. 이것이 바로이 방법을 Practical Way 라고 부르는 이유 입니다. 아니면 더 공정, 할 수 실용적인 방법. 이 답변은 다른 답변과 비교하여 실용적이라고 말하지 않기 때문에이 답변에 적합합니다. 객관적으로 사실입니다. 우리는 추론하기 쉬운 코드가 거의 없어 높은 수준의 실용성을 달성했습니다. 다른 코드에서는이 설명을 얻지 못했다고 말할 수 없습니다.

그것이 당신에게 “올바른”솔루션이됩니까? 그것은 당신 이 결정할 일입니다. 그리고 아무도 당신을 위해 그렇게 할 수 없습니다. 오직 당신 만이 당신의 요구를 알고 있습니다. 거의 모든 경우에, 나는 영리하고 빠른 종류보다 간단하고 실용적이며 다양한 코드를 소중하게 생각합니다. 당신이 중요하게 생각하는 것은 다를 수 있으므로 자신에게 맞는 것을 선택하십시오.


편집하다

나의 오래된 대답은 arrayEqual작은 절차로 분해하는 데 더 집중되었습니다 . 그것은 흥미로운 운동이지만 실제로이 문제에 접근하는 가장 (가장 실용적인) 방법은 아닙니다. 관심이 있으시면이 개정 내역을 볼 수 있습니다.


답변

원래 질문의 정신에서 :

두 배열을 이상적으로 효율적 으로 비교하고 싶습니다 . 공상 은 없으며, 동일하면 사실이고 그렇지 않으면 거짓입니다.

여기에 제안 된 더 간단한 제안 중 일부에 대해 성능 테스트를 실행하여 다음과 같은 결과를 얻었습니다 (빠르거나 느림).

반면 (67 %) 팀 아래로

var i = a1.length;
while (i--) {
    if (a1[i] !== a2[i]) return false;
}
return true

모든 (69 %) user2782196하여

a1.every((v,i)=> v === a2[i]);

감소 (74 %) DEIs하여

a1.reduce((a, b) => a && a2.includes(b), true);

Gaizka Allende & vivek의 join & toString (78 %)

a1.join('') === a2.join('');

a1.toString() === a2.toString();

Victor Palomo의 반 문자열 (90 %)

a1 == a2.toString();

radtek으로 stringify (100 %)

JSON.stringify(a1) === JSON.stringify(a2);

참고 배열은 단일 차원 배열을 분류하는 것으로 가정 아래의 예. .length공통 벤치 마크에 대한 비교가 제거되었습니다 ( a1.length === a2.length제안에 추가 하면 성능이 ~ 10 % 향상됩니다). 각각의 속도와 한계를 아는 데 가장 적합한 솔루션을 선택하십시오.

관련이없는 메모 : 사람들이이 질문에 완벽하게 합법적 인 답변을 내리기 위해 투표 버튼에서 모든 트리거 행복 John Waynes를 얻는 것이 흥미 롭습니다.