How to get filtered in and out elements of array at one go in Javascript
Asked Answered
U

6

10

I wonder if there is a precise way for getting filtered an unfiltered elements of an array in Javascript, I mean, like, in one go.

Currently, I use a logic like follows:

const myArray = ['a', 'b', 'c', 'd', 'e']
const filterArray = ['a', 'b']

// I want to combine those two expressions somehow
const filteredInResult = myArray.filter(e => filterArray.includes(e))
const filteredOutResult = myArray.filter(e => !filterArray.includes(e))

console.log(filteredInResult)
console.log(filteredOutResult)

I felt like a destructuring-like way might already be there to achieve it, but anyway, I prefer asking you guys if there is a way for getting filtered in & out results in one shot.

EDIT: SO keeps alerting me if this question is similar to the question here, but I used string comparison and includes for brewity above but the filtering expression may be more complex than that. So, the I must underline that the focus of the question is not on difference of two string arrays. I am leaving another example and hope the questions won't be merged :D

// A more complex use case
const myArray = [
  {id: 1, value: 'a'},
  {id: 2, value: 'b'},
  {id: 3, value: 'c'},
  {id: 4, value: 'd'},
  {id: 5, value: 'e'},
]
const filterArray = ['a', 'b']

// I want to combine those two expressions somehow
const filteredInResult = myArray.filter(e => filterArray.includes(e.value))
const filteredOutResult = myArray.filter(e => !filterArray.includes(e.value))

console.log(filteredInResult)
console.log(filteredOutResult)
Ululate answered 26/11, 2019 at 12:12 Comment(3)
Does this answer your question? How to get the difference between two arrays in JavaScript?Cushy
Realize the “one go” is contextual. In the answer and question example, the use of “includes” is doing a lookup (internal loop) with each iteration of the expressed loop. In no case would there ever be a “one go” solution.Latton
@JuhilSomaiya Nope, I edited and added another use case to show that it is not always the difference of arrays with similar structure.Ululate
L
7

If you're worried about iterating twice over the myArray, you might first consider reducing the computational complexity. Because each iteration of the loops calls Array.prototype.includes, and the complexity of Array.prototype.includes is O(n), your code has an overall complexity of O(n ^ 2). (outer loop: O(n) * inner loop: O(n)). So, consider fixing that first: use a Set and Set.has, an O(1) operation, instead of an array and .includes. This is assuming that your actual filterArray is large enough that computational complexity is something to worry about - sets do have a bit of an overhead cost.

As for the other (main) part of the question, one option is to create the two result arrays outside, then push to the appropriate one while iterating:

const myArray = ['a', 'b', 'c', 'd', 'e']
const filterArray = new Set(['a', 'b'])

const filteredInResult = [];
const filteredOutResult = [];
for (const e of myArray) {
  (filterArray.has(e) ? filteredInResult : filteredOutResult).push(e);
}
console.log(filteredInResult)
console.log(filteredOutResult)

Could also use reduce, though I don't think it looks very good:

const myArray = ['a', 'b', 'c', 'd', 'e']
const filterArray = new Set(['a', 'b'])

const { filteredInResult, filteredOutResult } = myArray.reduce((a, e) => {
  a[filterArray.has(e) ? 'filteredInResult' : 'filteredOutResult'].push(e);
  return a;
}, { filteredInResult: [], filteredOutResult: [] });

console.log(filteredInResult)
console.log(filteredOutResult)
Luong answered 26/11, 2019 at 12:14 Comment(0)
K
5

You could use .reduce() instead of .filter(), where you use the (numeric) boolean value of includes() as the index for your accumilator like so:

const myArray = ['a', 'b', 'c', 'd', 'e'];
const filterArray = ['a', 'b'];

const [fOut, fIn] = myArray.reduce((a, n) => {
  a[+filterArray.includes(n)].push(n); 
  return a;
}, [[], []]);

console.log(fIn);
console.log(fOut);
Kierkegaard answered 26/11, 2019 at 12:18 Comment(0)
M
4

Another (newer and slightly shorter) option is to use Object.groupBy:

const arr = ['a', 'b', 'c', 'd', 'e'];
const cond = ['a', 'b'];

const { fi, fo } = Object.groupBy(arr, e => cond.includes(e) ? "fi" : "fo");

console.log(fi, fo);

Note: The returned arrays may be undefined instead of empty (or both if the input array is empty).

Marcosmarcotte answered 5/4 at 1:9 Comment(1)
Object.groupBy is the simplest and most concise functional approach for this.Euphony
M
3

I could not destruct but this seems to be simpler to read than the reduce offered elsewhere

const myArray = ['a', 'b', 'c', 'd', 'e']
const filterArray = ['a', 'b']
let filteredOutResult = [];

const filteredInResult = myArray.filter(item => { 
   if (filterArray.includes(item)) return item; 
   filteredOutResult.push(item); 
});

console.log(filteredInResult,filteredOutResult)
 
Marchioness answered 26/11, 2019 at 12:21 Comment(0)
D
2

A solution with ramda's partition.

const
    array = ['a', 'b', 'c', 'd', 'e'],
    filter = ['a', 'b'],
    [filteredInResult, filteredOutResult] = R.partition(v => filter.includes(v), array);

console.log(...filteredInResult); // a b
console.log(...filteredOutResult) // c d e
<script src="http://cdn.jsdelivr.net/ramda/latest/ramda.min.js"></script>
Dasyure answered 26/11, 2019 at 12:36 Comment(0)
A
0

One native answer is to use Array#reduce:

const { in, out } = myArr.reduce((acc, v) => {
  (filterArray.includes(v) ? acc.in : acc.out).push(v);
  return acc;
}, { in: [], out: [] });

And then destructure the returned object

Adp answered 26/11, 2019 at 12:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.