How to get "unfiltered" array items? - javascript

Let's say I have an array which I filter by calling myItems.filter(filterFunction1) and get some items from it.
Then I want to run another filtering function filterFunction2 against the remaining items which were not selected by filterFunction1.
Is that possible to get the remaining items that were left out after calling a filtering function?

You'd have to rerun the filter with an inverted predicate, which seems wasteful. You should reduce the items instead and bin them into one of two bins:
const result = arr.reduce((res, item) => {
res[predicate(item) ? 'a' : 'b'].push(item);
return res;
}, { a: [], b: [] });
predicate here is the callback you'd give to filter.

Unfortunately, there is no one-step solution based on filter. Still the solution is a simple one-liner:
Here's an example
const arr = [ 1,2,3,4,5,6,7,8 ];
const filtered = arr.filter(x=>!!(x%2))
const remaining = arr.filter(x=>!filtered.includes(x))
console.log(filtered, remaining);

You could map an array of flags and then filter by the flags values.
const cond = v => !(v % 2);
var array = [1, 2, 3, 4, 5],
flags = array.map(cond),
result1 = array.filter((_, i) => flags[i]),
result2 = array.filter((_, i) => !flags[i]);
console.log(result1);
console.log(result2);

You can achieve that using Array.reduce.
const myItems = [...];
const { result1, result2 } = myItems.reduce(
(result, item) => {
if (filterFunc1(item)) {
result.result1.push(item);
} else if (filterFunc2(item)) {
result.result2.push(item);
}
return result;
},
{ result1: [], result2: [] },
);

If you don't want to use reduce, you may want to iterate the array once and acquire the filtered and unfiltered items in a single shot, using a plain efficient for..of loop:
function filterAndDiscriminate(arr, filterCallback) {
const res = [[],[]];
for (var item of arr) {
res[~~filterCallback(item)].push(item);
}
return res;
}
const [unfiltered, filtered] = filterAndDiscriminate([1,2,3,4,5], i => i <= 3);
console.log(filtered, unfiltered);

There's a way more simple and readable way to do this:
const array1 = []
const array2 = []
itemsToFilter.forEach(item => item.condition === met ? array1.push(challenge) : array2.push(challenge))

Related

How to get the excluded elements when using javascript's filter

Javascript's filter returns the array with all the elements passing the test.
How how can you easily get all the elements that failed the test without running the test again, but for the converse? How is the best way to do it, even if you have to run the test again.
let arr; // this is the array on which the filter will be run [SET ELSEWHERE]
let fn; // The filter function [SET ELSEWHERE]
let goodElements; // This will be the new array of the good elements passing the test
let badElements; // This will be the new array of the elements failing the test
goodElements = arr.filter(fn);
// SO HOW IS badElements set????
How is badElements set?
If you don't want to do two iterations, you can use a for loop and a ternary operator:
let arr = [1, 2, 3];
let fn = (e) => e % 2 == 0;
let goodElements = [];
let badElements = [];
for(const e of arr) (fn(e) ? goodElements : badElements).push(e);
console.log(goodElements);
console.log(badElements);
Otherwise, just invert the condition with the ! operator:
let arr = [1, 2, 3];
let fn = (e) => e % 2 == 0;
let goodElements;
let badElements;
goodElements = arr.filter(fn);
badElements = arr.filter(e => !fn(e));
console.log(goodElements);
console.log(badElements);
Don't use filter(). If you want to partition the data into two arrays, do it yourself.
function partition(array, fn) {
let goodArray = [],
badArray = [];
array.forEach(el => {
if (fn(el)) {
goodArray.push(el);
} else {
badArray.push(el);
}
});
return [goodArray, badArray];
}
let [goodElemements, badElements] = partition(arr, fn);
You could also use reduce()
function partition(array, fn) {
return array.reduce(acc, el => {
if (fn(el)) {
acc[0].push(el);
} else {
acc[1].push(el);
}
}, [[],[]]);
}
let [goodElemements, badElements] = partition(arr, fn);
If you want to do this strictly with Array.filter and only one loop, then consider something like this:
UPD: based on #AlvaroFlaƱoLarrondo comment, added external condition function to current approach.
// Array of elements
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
// External filter method
const fn = e => e.length > 6;
// Define empty bad array
const bad = [];
// Define good array as result from filter function
const good = words.filter(word => {
// In filter condition return good values
if(fn(word)) return word;
// And skip else values by just pushing them
// to bad array, without returning
else bad.push(word);
});
// Results
console.log(good);
console.log(bad);
You could use reduce in order to split the array into two arrays based on a predicate, like in this example:
const arr = [1, 0, true, false, "", "foo"];
const fn = element => !element;
const [goodElements, badElements] = arr.reduce(
([truthies, falsies], cur) =>
fn(cur) ? [truthies, [...falsies, cur]] : [[...truthies, cur], falsies],
[[], []]
);
console.log(goodElements, badElements);
I see two possible routes:
// in this case, if it's not in `goodElements`, it's a bad 'un
badElements = arr.filter( el => !goodElements.includes(el) );
or this:
// we don't **know** if fn needs the optional parameters, so we will
// simply pass them. If it doesn't need 'em, they'll be ignored.
badElements = arr.filter( (el, idx, arr) => !fn(el, idx, arr) );
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const fn = (x) => x % 2 === 0
const removeItems = (array, itemsToRemove) => {
return array.filter(v => {
return !itemsToRemove.includes(v);
});
}
const goodElements = arr.filter(fn)
console.log(goodElements) // [ 0, 2, 4, 6, 8 ]
const badElements = removeItems(arr, goodElements)
console.log(badElements) // [ 1, 3, 5, 7, 9 ]

How to make the grouping fastest, provided that the data in the array also show the object nodeJs

Hello this is my array;
arr =[{cNo:1,buyOrSel:'A',ip:192.168.1.1},{cNo:1,buyOrSel:'S',ip:192.168.1.1},{cNo:2,buyOrSel:'S',ip:192.168.1.2},{cNo:3,buyOrSel:'A',ip:192.168.1.1},{cNo:4,buyOrSel:'S',ip:192.168.1.3},{cNo:5,buyOrSel:'S',ip:192.168.1.2}]
I want to group in Object like this;
[{cNo:'1,3',ip:192.168.1.1},{cNo:'2,5',ip:192.168.1.2}]
I don't want to use nested For loop.What is the best way for this ?
let arr = [{cNo:1,buyOrSel:'A',ip:"192.168.1.1"},{cNo:1,buyOrSel:'S',ip:"192.168.1.1"},{cNo:2,buyOrSel:'S',ip:"192.168.1.2"},{cNo:3,buyOrSel:'A',ip:"192.168.1.1"},{cNo:4,buyOrSel:'S',ip:"192.168.1.3"},{cNo:5,buyOrSel:'S',ip:"192.168.1.2"}];
arr = arr.reduce((prev, a) => {
let cNo = prev[a.ip] = prev[a.ip] || [];
if (cNo.indexOf(a.cNo) < 0) {
prev[a.ip].push(a.cNo);
};
return prev
}, {});
arr = Object.keys(arr).map(key => {
return {
cNo: arr[key].join(),
ip: key
}
});
console.log(arr);
you can use object for better grouping.
and instead of pushing only cNo, you can push whole object too.
let arr = [{cNo:1,buyOrSel:'A',ip:"192.168.1.1"},{cNo:1,buyOrSel:'S',ip:"192.168.1.1"},{cNo:2,buyOrSel:'S',ip:"192.168.1.2"},{cNo:3,buyOrSel:'A',ip:"192.168.1.1"},{cNo:4,buyOrSel:'S',ip:"192.168.1.3"},{cNo:5,buyOrSel:'S',ip:"192.168.1.2"}];
let objectByIP = {};
arr.forEach(element => {
if (!objectByIP[element.ip]) {
objectByIP[element.ip] = [element.cNo];
} else {
objectByIP[element.ip].push(element.cNo);
}
});
console.log(objectByIP);
console.log(Object.keys(objectByIP));
you can use map():
let a = arr.map(a=>{ return {cNo: a.cNo, ip: a.ip}})

Insert key-Value pair to all entries of object by condition

I have a function with params and I am doing a forEach loop to add all the values from the loop.
const data = (sd) => Object.entries(obj).map(([k, g]) => ({
['name']: k,
['data']: g.map(entry => entry[sd]),
['type']: sd
}));
I also need to add another value but it is conditional.
I am then doing to make one giant set
let arr = ['abc', 'xyz'];
let x = [];
arr.forEach(y => {
x = [...x, ...data(y)];
});
console.log(x);
I also want to add another key-value pair to data then arr element is xyz.
I want to add ['id']: k but only when arr elem is xyz and then push it to x.
This is just an example how you can have a conditional key value in your data.
// Try edit message
const data = [1, 2, 3, 4, 5]
const data2 = data.map(item => {
let predefined = {
value: item,
};
if (item === 2) predefined.id = item;
return predefined;
})
console.log(data2)

Count the repetition of an element in an array using a function with one parameter

Good Day, I am trying to count how many times a particular element in an array appears. I tried but my code below counts only one of the array even if it appears more than once (this is not the problem). I want it to return the amount of time each element appears. For example
let arr = [1, 3, 2, 1];
this should return
{1:2} {3:1} {2:1}
My code returns 3 (as in it just doesn't count one twice)
How do i go about this?
Below is my code
function numberCount(number) {
let count = 0;
number.forEach(function (item, index) {
if (number.indexOf(item) == index) count++;
});
console.log(count);
}
While iterating over number (better to call it arr, it's an array, not a number), use an object to keep track of the number of times each number has occured so far. Then, iterate over the resulting object's entries to create the objects desired:
let arr = [1, 3, 2, 1];
function numberCount(arr) {
let count = 0;
const obj = arr.reduce((a, num) => {
a[num] = (a[num] || 0) + 1;
return a;
}, {});
return Object.entries(obj).map(([key, val]) => ({ [key]: val }));
}
console.log(numberCount(arr));
Numeric keys always come in numeric order in an object. If you want the objects in the output to come in insertion order (eg, the object with key 3 before the object with key 2), then use a Map instead of an object (map keys will be iterated over in insertion order):
let arr = [1, 3, 2, 1];
function numberCount(arr) {
let count = 0;
const map = arr.reduce((a, num) => (
a.set(num, (a.get(num) || 0) + 1)
), new Map());
return [...map.entries()]
.map(([key, val]) => ({ [key]: val }));
}
console.log(numberCount(arr));
You should filter out these numbers, then use the length:
let arr = [1, 3, 2, 1];
function itemCount(array) {
var sorted = array.sort()
var uniqueCount = sorted.filter((v, i, a) => a.indexOf(v) == i);
var count = [];
uniqueCount.forEach(item => {
var itemCount = sorted.filter(e => e == item).length;
count.push({[item]: itemCount});
});
return count;
}
console.log(itemCount(arr));
I would suggest not reinventing the wheel, and instead use lodash which already has this function. Using countBy() you will get an object you can then convert into your desired result. For example:
const arr = [1, 3, 2, 1]
const count = _.countBy(arr)
const result = Object.keys(count).map(k => ({ [k]: count[k] }))
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>

What is the most efficent way to filter an object with an array of arrays?

I'm trying to filter an Object by an array of arrays, getting back an array of objects.
Like this:
let obj =
{
"a.1":1,
"a.2":2,
"b.1":3,
"b.2":4,
"c.1":5,
"c.2":6
}
let array =
[
["a.1","b.1"],
["a"],
["b","c.1"]
]
let expectedResult =
[
{
"a.1":1,
"b.1":3,
},
{
"a.1":1,
"a.2":2,
},
{
"b.1":3,
"b.2":4,
"c.1":5
},
]
// this is what I came up with
const filterObjectByArray = (obj, arr) =>
Object.keys(obj)
.filter(ch => {
for (var index = 0; index < arr.length; index++)
if (ch.startsWith(arr[index]))
return true;
})
.reduce((ret, key) =>{
ret[key] = obj[key]
return ret
},{})
let result = array.map(arr => filterObjectByArray(obj, arr))
//kind of deepEqual
console.log(JSON.stringify(expectedResult) == JSON.stringify(result))
Is there a easier or more convenient way to do that? I need to do this operation quite often and my object will be up to couple hundreds entries big, so I see a potential bottleneck here.
I would create a one type mapping of the "base" (the letter) to the "real" keys, and then use it to translate the letter to the real keys when create the object.
const obj = {
"a.1": 1,
"a.2": 2,
"b.1": 3,
"b.2": 4,
"c.1": 5,
"c.2": 6
};
const array = [
["a.1", "b.1"],
["a"],
["b", "c.1"]
];
const getBaseKey = (key) => key.match(/^[a-z]+/)[0]; // get the base of the key - the letter. If it's only one letter, you can use key[0]
/** create a one time map of keys by their base **/
const oobjKeysMap = Object.keys(obj).reduce((map, key) => {
const baseKey = getBaseKey(key);
const curr = map.get(baseKey) || [];
curr.push(key);
return map.set(baseKey, curr);
}, new Map());
const result = array.map((sub) => // iterate the array
[].concat(...sub.map((k) => k in obj ? k : oobjKeysMap.get(getBaseKey(k)))) // create the least of "real" keys
.reduce((ret, key) => { // create the object
ret[key] = obj[key];
return ret;
}, {})
);
console.log(result);

Categories