필터 함수로 배열 나누기


92

각 요소에 대해 호출 된 함수가 true또는을 반환하는지 여부에 따라 두 개로 분할하려는 Javascript 배열이 false있습니다. 기본적으로 이것은이다 array.filter, 그러나 나는 또한 손에 필터링 된 요소를 가지고 싶습니다 아웃 .

현재 내 계획은 array.forEach각 요소에 대해 조건 자 함수 를 사용 하고 호출하는 것입니다. 이것이 참인지 거짓인지에 따라 현재 요소를 두 개의 새로운 배열 중 하나에 푸시합니다. 더 우아하거나 더 나은 방법이 있습니까? 예 를 들어 array.filter는 반환하기 전에 요소를 다른 배열로 푸시합니다 false.


샘플 코드를 게시 할 수 있다면 더 나은 답변을 드릴 수 있습니다!
Mark Pieszak-Trilon.io

Javascript를 사용하는 구현에 관계없이 항상 항목을 반복하고, 함수를 실행하고, 항목을 배열로 푸시해야합니다. 더 효율적으로 만드는 방법 없다고 생각 합니다.
TheZ

3
전달 된 콜백에서 원하는 것은 무엇이든 할 수 .filter있지만 이러한 부작용은 추적하고 이해하기 어렵습니다. 배열을 반복하고 한 배열 또는 다른 배열로 푸시하십시오.
Felix Kling 2012

답변:


75

ES6를 사용하면 reduce로 스프레드 구문을 사용할 수 있습니다.

function partition(array, isValid) {
  return array.reduce(([pass, fail], elem) => {
    return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
  }, [[], []]);
}

const [pass, fail] = partition(myArray, (e) => e > 5);

또는 한 줄로 :

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);

8
나를 위해, lodash 파티션하거나 대해 forEach 이해하기 쉽게,하지만 그럼에도 불구하고, 좋은 노력을 할 것입니다
토니 리

15
이렇게하면 원본의 모든 요소에 대해 두 개의 새로운 배열이 생성됩니다. 한 배열에는 두 개의 요소 만 있지만 다른 배열은 배열의 크기에 따라 증가합니다. 따라서 이것은 매우 느리고 많은 메모리를 낭비합니다. (당신은 푸시와 동일한 기능을 수행 할 수 있으며보다 효율적으로 될 것입니다.)
스튜어트있는 Schechter에게

감사합니다. 시간을 절약했습니다!
7urkm3n

이것은 정말 유용합니다, 브라자. 기타 참고 사항 : 아래에 좀 더 읽기 쉬운 버전이 있습니다.
Raffi

38

lodash.partition 을 사용할 수 있습니다.

var users = [
  { 'user': 'barney',  'age': 36, 'active': false },
  { 'user': 'fred',    'age': 40, 'active': true },
  { 'user': 'pebbles', 'age': 1,  'active': false }
];

_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]

// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]

// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]

// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]

또는 ramda.partition

R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]

R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' }  ]

16

이를 위해 reduce를 사용할 수 있습니다.

function partition(array, callback){
  return array.reduce(function(result, element, i) {
    callback(element, i, array) 
      ? result[0].push(element) 
      : result[1].push(element);

        return result;
      }, [[],[]]
    );
 };

최신 정보. ES6 구문을 사용하면 재귀를 사용하여 수행 할 수도 있습니다.

function partition([current, ...tail], f, [left, right] = [[], []]) {
    if(current === undefined) {
        return [left, right];
    }
    if(f(current)) {
        return partition(tail, f, [[...left, current], right]);
    }
    return partition(tail, f, [left, [...right, current]]);
}

3
IMHO, 첫 번째 솔루션 (푸시)은 어레이 크기가 증가함에 따라 성능이 향상됩니다.
ToolmakerSteve

@ToolmakerSteve 왜 더 자세히 설명해 주시겠습니까? 나는 또한 상위 답변에 대한 의견을 읽었지만 여전히 왜 혼란스러워
buncis

2
@buncis. 첫 번째 접근 방식은 각 요소를 한 번 검사하여 해당 요소를 적절한 배열로 푸시합니다. 두 번째 접근법 은 각 요소에 대해 [...left, current]또는 [...right, current]-를 구성합니다. 정확한 내부 구조는 모르지만 단순히 요소를 배열에 밀어 넣는 것보다 구성이 더 비싸다고 확신합니다. 또한 일반적으로 재귀 는 매번 "스택 프레임"을 생성하기 때문에 반복 보다 비용이 많이 듭니다 .
ToolmakerSteve

14

이것은 Ruby의Enumerable#partition 방법 과 매우 유사하게 들립니다 .

함수가 부작용을 가질 수없는 경우 (즉, 원래 배열을 변경할 수없는 경우), 각 요소를 반복하고 요소를 두 배열 중 하나로 푸시하는 것보다 배열을 분할하는 더 효율적인 방법은 없습니다.

즉, Array이 기능을 수행 하는 방법을 만드는 것이 틀림없이 더 "우아하다" . 이 예제에서 필터 함수는 원래 배열의 컨텍스트에서 실행되고 (즉, this원래 배열이 됨) 요소와 요소의 인덱스를 인수로받습니다 ( jQuery의 each메서드 와 유사 함 ).

Array.prototype.partition = function (f){
  var matched = [],
      unmatched = [],
      i = 0,
      j = this.length;

  for (; i < j; i++){
    (f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
  }

  return [matched, unmatched];
};

console.log([1, 2, 3, 4, 5].partition(function (n, i){
  return n % 2 == 0;
}));

//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]

11
현대 독자의 경우 글로벌 표준 라이브러리 개체에 메서드를 추가하지 마십시오. 위험하고 덮어 쓸 가능성이있어 신비스럽고 깨진 행동으로 이어집니다. 적절하게 범위가 지정된 평범한 오래된 함수가 훨씬 안전하며 myFunc (array)를 호출하는 것은 array.myFunc ()보다 "우아하다".
Emmett R.

14

이 작은 녀석을 생각해 냈습니다. 그것은 당신이 설명한 것과 같은 모든 것을 사용하지만 제 생각에는 깨끗하고 간결 해 보입니다.

//Partition function
function partition(array, filter) {
  let pass = [], fail = [];
  array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
  return [pass, fail];
}

//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);

//Output
console.log(lessThan5);
console.log(greaterThanEqual5);


1
이 솔루션은 더 많은 찬성표를 가진 IMHO보다 훨씬 낫습니다. 읽기 쉽고 배열을 한 번만 통과하며 파티션 결과 배열을 할당 및 재할 당하지 않습니다. 또한 필터 함수 (값, 인덱스 및 전체 배열)에 세 가지 공통 필터 값을 모두 노출하는 것도 좋습니다. 이렇게하면이 기능을 훨씬 더 재사용 할 수 있습니다.
speckledcarp

나는 또한 단순함과 우아함 때문에 이것을 매우 좋아합니다. 그렇게 말하면서 for@qwertymk의 이전 답변에서와 같이 간단한 루프 보다 훨씬 느리다는 것을 알았습니다. 예를 들어 100,000 요소를 포함하는 배열의 경우 내 시스템에서 두 배 느 렸습니다.
tromgy

9

필터 함수에서 거짓 항목을 함수 외부의 다른 변수로 푸시 할 수 있습니다.

var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});

물론 value === false실제 비교가 필요합니다;)

그러나 그것은 forEach. forEach더 나은 코드 가독성을 위해 사용해야한다고 생각합니다 .


위의 포스터에 동의합니다. 이런 종류의 논리를 필터 함수에 넣는 것은 약간 부풀어 오르고 관리하기가 어렵습니다.
theUtherSide

난 당신이 그것 때문에 원래의 배열에서 필터를 제거하려면, 필터 말이 생각
Mojimi

5

읽기 쉽습니다.

const partition = (arr, condition) => {
    const trues = arr.filter(el => condition(el));
    const falses = arr.filter(el => !condition(el));
    return [trues, falses];
};

// sample usage
const nums = [1,2,3,4,5,6,7]
const [evens, odds] = partition(nums, (el) => el%2 == 0)

6
단점은 두 개의 루프를 대신 하나만 만드는 것입니다
Laurent

가독성을 위해
찬성 투표

4

이 시도:

function filter(a, fun) {
    var ret = { good: [], bad: [] };
    for (var i = 0; i < a.length; i++)
        if (fun(a[i])
            ret.good.push(a[i]);
        else
            ret.bad.push(a[i]);
    return ret;
}

데모


배열의 필터 기능이 실제로 모든 브라우저에서 지원되는 것은 아니라는 점을 지적합니다 (이전 IE를
찾고 있습니다

4

이건 어때?

[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )

아마도 이것은 스프레드 연산자보다 더 효율적일 것입니다.

또는 조금 더 짧지 만 더 못 생겼습니다.

[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )


2

여기에서 많은 답변 Array.prototype.reduce을 사용하여 가변 누산기를 구축하고 큰 배열의 경우 스프레드 연산자를 사용하여 반복 할 때마다 새 배열을 복사하는 것보다 더 효율적임을 지적합니다. 단점은 짧은 람다 구문을 사용하는 "순수한"표현식만큼 예쁘지 않다는 것입니다.

그러나 그 방법은 쉼표 연산자를 사용하는 것입니다. C와 유사한 언어에서 쉼표는 항상 오른손 피연산자를 반환하는 연산자입니다. 이를 사용하여 void 함수를 호출하고 값을 반환하는 식을 만들 수 있습니다.

function partition(array, predicate) {
    return array.reduce((acc, item) => predicate(item)
        ? (acc[0].push(item), acc)
        : (acc[1].push(item), acc), [[], []]);
}

부울 표현식이 암시 적으로 0과 1로 숫자로 캐스팅된다는 사실을 활용하면 읽을 수 있다고 생각하지 않지만 훨씬 더 간결하게 만들 수 있습니다.

function partition(array, predicate) {
    return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}

용법:

const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']

0

ONE-LINER 파티션

const partition = (a,f)=>a.reduce((p,q)=>(p[+!f(q)].push(q),p),[[],[]]);

데모

// to make it consistent to filter pass index and array as arguments
const partition = (a, f) =>
    a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

console.log(partition([1, 2, 3, 4, 5], x => x % 2 === 0));
console.log(partition([..."ABCD"], (x, i) => i % 2 === 0));

Typescript 용

const partition = <T>(
  a: T[],
  f: (v: T, i?: number, ar?: T[]) => boolean
): [T[], T[]] =>
  a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

-1

이해하기 쉽기 때문에 (그리고 타이프 스크립트로 완전히 타이핑 되었기 때문에) 이렇게했습니다.

const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => {
  const pass: T[] = []
  const fail: T[] = []
  array.forEach(element => {
    if (isValid(element)) {
      pass.push(element)
    } else {
      fail.push(element)
    }
  })
  return [pass, fail]
}

// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.