Javascript 배열을 사용하여 집합 차이를 계산하는 가장 빠르고 우아한 방법은 무엇입니까?


103

하자 AB두 세트합니다. 나는 그들 사이 의 세트 차이 ( 또는 선호도에 따라) 를 계산하는 정말 빠르고 우아한 방법을 찾고 있습니다. 두 세트는 제목에서 알 수 있듯이 Javascript 배열로 저장 및 조작됩니다.A - BA \B

노트:

  • Gecko 특정 트릭은 괜찮습니다.
  • 나는 기본 기능을 고수하는 것을 선호합니다 (하지만 더 빠르면 가벼운 라이브러리에 열려 있습니다)
  • 나는 보았지만 테스트하지는 않았지만 JS.Set (이전 요점 참조)

편집 : 중복 요소가 포함 된 세트에 대한 주석을 발견했습니다. 내가 "set"이라고 말할 때 나는 (다른 것들 중에서) 중복 요소를 포함하지 않는다는 것을 의미하는 수학적 정의를 의미합니다.


사용중인 "차이 설정"용어는 무엇입니까? 그게 C ++에서 나온 건가요?
Josh Stodola

세트에는 무엇이 있습니까? 목표로 삼는 유형 (예 : 숫자)에 따라 세트 차이 계산이 정말 빠르고 우아 하게 수행 될 수 있습니다 . 집합에 DOM 요소가 포함되어 있으면 indexOf구현 속도가 느려질 것입니다.
Crescent Fresh

@Crescent : 내 세트에는 숫자가 포함되어 있습니다. 지정하지 않아서 죄송합니다. @Josh : 수학의 표준 집합 연산입니다 ( en.wikipedia.org/wiki/Set_%28mathematics%29#Complements )
Matt Ball

@JoshStodola는 집합 차이
Pat

1
@MattBall 아니, 나는 그것을 보았다. 하지만 Josh의 질문은 유효하고 답이 없었기 때문에 대답했습니다. :)
Pat

답변:


173

이것이 가장 효과적인지 모르겠지만 아마도 가장 짧은

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(function(x) { return B.indexOf(x) < 0 })

console.log(diff);

ES6로 업데이트 :

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(x => !B.includes(x) );

console.log(diff);

8
+1 : 가장 효율적인 솔루션은 아니지만 확실히 짧고 읽기
Christoph

10
참고 : array.filter는 브라우저 간 지원되지 않습니다 (예 : IE에서는 지원되지 않음). @Matt는 "Gecko 전용 트릭은 괜찮다"고 말했기 때문에 중요하지 않은 것 같지만 언급 할 가치가 있다고 생각합니다.
Eric Bréchemier

44
이것은 매우 느립니다. O (| A | * | B |)
glebm 2013

1
@ EricBréchemier 이제 지원됩니다 (IE 9부터). Array.prototype.filter 는 표준 ECMAScript 기능입니다.
Quentin Roy

5
ES6에서는 !B.includes(x)대신 사용할 수 있습니다. B.indexOf(x) < 0:)
c24w

86

글쎄, 7 년 후, ES6의 Set 객체를 사용하면 매우 쉽고 (하지만 여전히 파이썬 만큼 콤팩트하지는 않습니다 A - B) indexOf큰 배열 보다 빠르다고 합니다.

console.clear();
let a = new Set([1, 2, 3, 4]);
let b = new Set([5, 4, 3, 2]);


let a_minus_b = new Set([...a].filter(x => !b.has(x)));
let b_minus_a = new Set([...b].filter(x => !a.has(x)));
let a_intersect_b = new Set([...a].filter(x => b.has(x))); 

console.log([...a_minus_b]) // {1}
console.log([...b_minus_a]) // {5}
console.log([...a_intersect_b]) // {2,3,4}


1
또한 큰 배열의 경우 indexOf보다 훨씬 빠릅니다.
Estus 플라스크

100
자바 스크립트 세트에 통합 / 교차 / 차이가 내장되지 않은 이유는 저를 넘어서는 것입니다 ...
SwiftsNamesake

6
완전히 동의 해; 이것들은 js 엔진에서 구현 된 낮은 수준의 프리미티브 여야합니다. 그것은 또한 나를 넘어서 ...
Rafael

4
@SwiftsNamesake 2018 년 1github.com/tc39/agendas/blob/master/2018/01.md 에서 논의 될 내장 메서드 설정에 대한 제안이 있습니다 .

15

객체를지도로 사용하여 user187291의 답변 에서 BA같이의 각 요소를 선형으로 스캔하지 않도록 할 수 있습니다 .

function setMinus(A, B) {
    var map = {}, C = [];

    for(var i = B.length; i--; )
        map[B[i].toSource()] = null; // any other value would do

    for(var i = A.length; i--; ) {
        if(!map.hasOwnProperty(A[i].toSource()))
            C.push(A[i]);
    }

    return C;
}

비표준 toSource()방법 은 고유 한 속성 이름을 가져 오는 데 사용됩니다. 모든 요소에 이미 고유 한 문자열 표현이있는 경우 (숫자의 경우처럼) toSource()호출 을 삭제하여 코드 속도를 높일 수 있습니다 .


9

jQuery를 사용하는 가장 짧은 방법은 다음과 같습니다.

var A = [1, 2, 3, 4];
var B = [1, 3, 4, 7];

var diff = $(A).not(B);

console.log(diff.toArray());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


이것은 차이의 객체를 반환합니다.
Drew Baker

2
not3.0.0-rc1부터 jQuery 는 더 이상 일반 객체에서 작동하지 않습니다. 참조 github.com/jquery/jquery/issues/3147
마크 - 앙드레 Lafortune

2
그것은 ~ 70K 3 자 라이브러리에 종속성을 추가 할 수있는 좋은 생각이 아니다 단지 여기에 다른 답변에서와 같이 같은 일이 몇 줄의 코드에서 수행 할 수 있기 때문에,이 작업을 수행 할 수 있습니다. 그러나 프로젝트에서 이미 jQuery를 사용하고 있다면 제대로 작동합니다.
CBarr

이 접근법은 코드가 적지 만 다른 알고리즘의 공간 및 시간 복잡성과 방법을 수행하는 데 사용하는 데이터 구조에 대한 설명은 제공하지 않습니다. 개발자가 데이터 확장 또는 제한된 메모리가 허용 될 때 평가없이 소프트웨어를 엔지니어링 할 수 있도록 블랙 박스로 표시됩니다. 대용량 데이터 세트에 이러한 접근 방식을 사용하면 소스 코드를 추가로 조사 할 때까지 성능이 알려지지 않을 수 있습니다.
Downhillski

이것은 B에없는 A 요소의 양 (이 경우 2)을 반환하는 것입니다. 2를 배열로 변환하는 것은 무의미합니다 ...
Alex

6

배열 B를 해시 한 다음 B에없는 배열 A의 값을 유지합니다.

function getHash(array){
  // Hash an array into a set of properties
  //
  // params:
  //   array - (array) (!nil) the array to hash
  //
  // return: (object)
  //   hash object with one property set to true for each value in the array

  var hash = {};
  for (var i=0; i<array.length; i++){
    hash[ array[i] ] = true;
  }
  return hash;
}

function getDifference(a, b){
  // compute the difference a\b
  //
  // params:
  //   a - (array) (!nil) first array as a set of values (no duplicates)
  //   b - (array) (!nil) second array as a set of values (no duplicates)
  //
  // return: (array)
  //   the set of values (no duplicates) in array a and not in b, 
  //   listed in the same order as in array a.

  var hash = getHash(b);
  var diff = [];
  for (var i=0; i<a.length; i++){
    var value = a[i];
    if ( !hash[value]){
      diff.push(value);
    }
  }
  return diff;
}

그것은 제가 30 분 전에 게시 한 것과 똑같은 알고리즘입니다
Christoph

@Christoph : 당신 말이 맞아요 ... 나는 그것을 알아 차리지 못했습니다. 나는 :)하지만 이해하기 더 간단 내 구현을 찾을 수
에릭 Bréchemier

여러 번 재사용 할 수 있도록 getDifference 외부의 차이를 계산하는 것이 더 낫다고 생각합니다. 선택 사항 일 수도 있습니다. getDifference(a, b, hashOfB), 전달되지 않으면 계산되고 그렇지 않으면 그대로 재사용됩니다.
Christophe Roussy

4

Christoph의 아이디어를 통합하고 배열 및 객체 / 해시 ( each및 친구)에 대한 몇 가지 비표준 반복 방법을 가정하면 총 약 20 줄의 선형 시간에서 차이, 결합 및 교차를 설정할 수 있습니다.

var setOPs = {
  minusAB : function (a, b) {
    var h = {};
    b.each(function (v) { h[v] = true; });
    return a.filter(function (v) { return !h.hasOwnProperty(v); });
  },
  unionAB : function (a, b) {
    var h = {}, f = function (v) { h[v] = true; };
    a.each(f);
    b.each(f);
    return myUtils.keys(h);
  },
  intersectAB : function (a, b) {
    var h = {};
    a.each(function (v) { h[v] = 1; });
    b.each(function (v) { h[v] = (h[v] || 0) + 1; });
    var fnSel = function (v, count) { return count > 1; };
    var fnVal = function (v, c) { return v; };
    return myUtils.select(h, fnSel, fnVal);
  }
};

이것은 그 가정 eachfilter배열에 대해 정의하고 있으며, 우리는 두 개의 유틸리티 방법을 가지고 :

  • myUtils.keys(hash): 해시의 키가있는 배열을 반환합니다.

  • myUtils.select(hash, fnSelector, fnEvaluator): true fnEvaluatorfnSelector반환 하는 키 / 값 쌍을 호출 한 결과가있는 배열 을 반환합니다.

select()느슨하게 커먼 리스프에서 영감을, 그리고 단지입니다 filter()map()하나에 굴렀다. (에서 정의하는 Object.prototype것이 좋지만 그렇게하면 jQuery가 혼란스러워서 정적 유틸리티 메서드에 정착했습니다.)

성능 : 테스트

var a = [], b = [];
for (var i = 100000; i--; ) {
  if (i % 2 !== 0) a.push(i);
  if (i % 3 !== 0) b.push(i);
}

50,000 및 66,666 요소가있는 두 세트를 제공합니다. 이 값으로 AB는 약 75ms가 걸리고 결합과 교차는 각각 약 150ms가 걸립니다. (Mac Safari 4.0, 타이밍에 Javascript Date 사용)

20 줄의 코드에 대한 적절한 보상이라고 생각합니다.


1
hasOwnProperty()요소가 숫자 인 경우에도 여전히 확인해야합니다 . 그렇지 않으면 Object.prototype[42] = true;수단 과 같은 42것이 결과 집합에서 절대 발생할 수 없습니다
Christoph

그런 식으로 42를 설정할 수 있지만 실제로 그렇게 할 수있는 반 현실적인 사용 사례가 있습니까? 그러나 일반적인 문자열의 경우 요점을 취합니다. 일부 Object.prototype 변수 또는 함수와 쉽게 충돌 할 수 있습니다.
jg-faustus 2009


3

@milan의 답변에서 빌린 몇 가지 간단한 기능 :

const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x)));
const setIntersection = (a, b) => new Set([...a].filter(x => b.has(x)));
const setUnion = (a, b) => new Set([...a, ...b]);

용법:

const a = new Set([1, 2]);
const b = new Set([2, 3]);

setDifference(a, b); // Set { 1 }
setIntersection(a, b); // Set { 2 }
setUnion(a, b); // Set { 1, 2, 3 }

2

금식 방법은 그렇게 우아하지는 않지만 확인하기 위해 몇 가지 테스트를 실행했습니다. 하나의 어레이를 객체로로드하면 대량으로 처리하는 것이 훨씬 빠릅니다.

var t, a, b, c, objA;

    // Fill some arrays to compare
a = Array(30000).fill(0).map(function(v,i) {
    return i.toFixed();
});
b = Array(20000).fill(0).map(function(v,i) {
    return (i*2).toFixed();
});

    // Simple indexOf inside filter
t = Date.now();
c = b.filter(function(v) { return a.indexOf(v) < 0; });
console.log('completed indexOf in %j ms with result %j length', Date.now() - t, c.length);

    // Load `a` as Object `A` first to avoid indexOf in filter
t = Date.now();
objA = {};
a.forEach(function(v) { objA[v] = true; });
c = b.filter(function(v) { return !objA[v]; });
console.log('completed Object in %j ms with result %j length', Date.now() - t, c.length);

결과 :

completed indexOf in 1219 ms with result 5000 length
completed Object in 8 ms with result 5000 length

그러나 이것은 문자열에서만 작동 합니다 . 번호가 매겨진 집합을 비교하려는 경우 결과를 parseFloat 으로 매핑 할 수 있습니다.


1
b.filter(function(v) { return !A[v]; });두 번째 함수에서 c =이어야하지 않습니까?
fabianmoronzirfas

당신이 올바른지. 왠지 더 빠른 것 같습니다
SmujMaiku

1

이것은 작동하지만 다른 하나도 훨씬 더 짧고 우아하다고 생각합니다.

A = [1, 'a', 'b', 12];
B = ['a', 3, 4, 'b'];

diff_set = {
    ar : {},
    diff : Array(),
    remove_set : function(a) { ar = a; return this; },
    remove: function (el) {
        if(ar.indexOf(el)<0) this.diff.push(el);
    }
}

A.forEach(diff_set.remove_set(B).remove,diff_set);
C = diff_set.diff;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.