Join an array by commas and "and" - javascript

I want to convert the array ['one', 'two', 'three', 'four'] into one, two, three and four
Note that the first items have a comma, and but there is the word and between the second-last one and the last one.
The best solution I've come up with:
a.reduce( (res, v, i) => i === a.length - 2 ? res + v + ' and ' : res + v + ( i == a.length -1? '' : ', '), '' )
It's based on adding the commas at the end -- with the exception of the second-last one (a.length - 2) and with a way to avoid the last comma (a.length - 2).
SURELY there must be a better, neater, more intelligent way to do this?
It's a difficult topic to search on search engines because it contains the word "and"...

One option would be to pop the last item, then join all the rest by commas, and concatenate with and plus the last item:
const input = ['one', 'two', 'three', 'four'];
const last = input.pop();
const result = input.join(', ') + ' and ' + last;
console.log(result);
If you can't mutate the input array, use slice instead, and if there might only be one item in the input array, check the length of the array first:
function makeString(arr) {
if (arr.length === 1) return arr[0];
const firsts = arr.slice(0, arr.length - 1);
const last = arr[arr.length - 1];
return firsts.join(', ') + ' and ' + last;
}
console.log(makeString(['one', 'two', 'three', 'four']));
console.log(makeString(['one']));

Starting in V8 v7.2 and Chrome 72, you can use the sweet Intl.ListFormat API. It will also take care of localizing your list when requested, which might be of great help if you need it.
const lf = new Intl.ListFormat('en');
console.log(lf.format(['Frank']));
// → 'Frank'
console.log(lf.format(['Frank', 'Christine']));
// → 'Frank and Christine'
console.log(lf.format(['Frank', 'Christine', 'Flora']));
// → 'Frank, Christine, and Flora'
console.log(lf.format(['Frank', 'Christine', 'Flora', 'Harrison']));
// → 'Frank, Christine, Flora, and Harrison'
// You can use it with other locales
const frlf = new Intl.ListFormat('fr');
console.log(frlf.format(['Frank', 'Christine', 'Flora', 'Harrison']));
// → 'Frank, Christine, Flora et Harrison'
You can even specify options to make it a disruption and use "or" instead of "and", or to format units such as "3 ft, 7 in".
It's not very widely supported as of writing, so you might not want to use it everywhere.
References
The Intl.ListFormat API - Google Developers
V8 release v7.2

I like Mark Meyer's approach as it doesn't alter the input. Here's my spin:
const makeCommaSeparatedString = (arr, useOxfordComma) => {
const listStart = arr.slice(0, -1).join(', ')
const listEnd = arr.slice(-1)
const conjunction = arr.length <= 1
? ''
: useOxfordComma && arr.length > 2
? ', and '
: ' and '
return [listStart, listEnd].join(conjunction)
}
console.log(makeCommaSeparatedString(['one', 'two', 'three', 'four']))
// one, two, three and four
console.log(makeCommaSeparatedString(['one', 'two', 'three', 'four'], true))
// one, two, three, and four
console.log(makeCommaSeparatedString(['one', 'two'], true))
// one and two
console.log(makeCommaSeparatedString(['one']))
// one
console.log(makeCommaSeparatedString([]))
//

You can use Array.prototype.slice() when array.length is bigger than 1 and exclude the rest of the cases:
const result = a => a.length > 1
? `${a.slice(0, -1).join(', ')} and ${a.slice(-1)}`
: {0: '', 1: a[0]}[a.length];
Code example:
const input1 = ['one', 'two', 'three', 'four'];
const input2 = ['A Tale of Two Cities', 'Harry Potter and the smth', 'One Fish, Two Fish, Red Fish, Blue Fish'];
const input3 = ['one', 'two'];
const input4 = ['one'];
const input5 = [];
const result = a => a.length > 1
? `${a.slice(0, -1).join(', ')} and ${a.slice(-1)}`
: {0: '', 1: a[0]}[a.length];
console.log(result(input1));
console.log(result(input2));
console.log(result(input3));
console.log(result(input4));
console.log(result(input5));

Using Array#reduce:
['one', 'two', 'three', 'four'].reduce(
(a, b, i, array) => a + (i < array.length - 1 ? ', ' : ' and ') + b)

Another approach could be using the splice method to remove the last two elements of the array and join they using the and token. After this, you could push this result again on the array, and finally join all elements using the , separator.
Updated to:
1) Show how this works for multiple cases (no extra control needed over the array length).
2) Wrap the logic inside a method.
3) Do not mutate the original array (if not required).
let arrayToCustomStr = (arr, enableMutate) =>
{
// Clone the received array (if required).
let a = enableMutate ? arr : arr.slice(0);
// Convert the array to custom string.
let removed = a.splice(-2, 2);
a.push(removed.join(" and "));
return a.join(", ");
}
// First example, mutate of original array is disabled.
let input1 = ['one', 'two', 'three', 'four'];
console.log("Result for input1:" , arrayToCustomStr(input1));
console.log("Original input1:", input1);
// Second example, mutate of original array is enabled.
let input2 = ['one', 'two'];
console.log("Result for input2:", arrayToCustomStr(input2, true));
console.log("Original input2:", input2);
// Third example, lenght of array is 1.
let input3 = ['one'];
console.log("Result for input3:", arrayToCustomStr(input3));
// Fourth example, empty array.
let input4 = [];
console.log("Result for input4:", arrayToCustomStr(input4));
// Plus example.
let bob = [
"Don't worry about a thing",
"Cause every little thing",
"Gonna be all right",
"Saying, don't worry about a thing..."
];
console.log("Result for bob:", arrayToCustomStr(bob));
.as-console-wrapper {
top: 0px;
max-height: 100% !important;
}

Intl.ListFormat is exactly what you want. Although only Chrome 72+ and Opera 60+ are supported in May, 2019, a polyfill is available for other browsers: https://github.com/zbraniecki/IntlListFormat
const list = ['A', 'B', 'C', 'D'];
// With Oxford comma
const lfOxfordComma = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction'
});
console.log(lfOxfordComma.format(list)); // → A, B, C, and D
// Without Oxford comma
const lfComma = new Intl.ListFormat('en-GB', {
style: 'long',
type: 'conjunction'
});
console.log(lfComma.format(list)); // → A, B, C and D

Here's a one-line option that is similar to Yosvel Quintero Arguelles's answer but provides an Oxford comma when there are three or more items.
let resultA4 = (list => list.length < 3 ? list.join(" and ") : [list.pop(), list.join(", ")].reverse().join(", and ")).call(this, ['one', 'two', 'three', 'four']);
let resultA2 = (list => list.length < 3 ? list.join(" and ") : [list.pop(), list.join(", ")].reverse().join(", and ")).call(this, ['one', 'two']);
let resultA1 = (list => list.length < 3 ? list.join(" and ") : [list.pop(), list.join(", ")].reverse().join(", and ")).call(this, ['one']);
let items = ['one', 'two', 'three', 'four'];
//If you can't mutate the list you can do this
let resultB = (list => list.length < 3 ? list.join(" and ") : [list.pop(), list.join(", ")].reverse().join(", and ")).call(this, items.slice());
// or this option that doesn't use call
let resultC = items.length < 3 ? items.join(" and ") : [items.slice(0, -1).join(", "), items.slice(-1)].join(", and ");
console.log(resultA4);
console.log(resultA2);
console.log(resultA1);
console.log(resultB);
console.log(resultC);

An easy way is also to insert and before the last word or quoted string using regex. Answer here on stack overflow

Related

How to create an array of price-ranges from an array of price-values?

I have an array of prices
I would like to group these prices into ranges if they are within 2 of each other
How do I achieve this
// present array
const array = [
'3','5','6','12','17','22'
]
// the result I want
const array_ranges = [
'3-6', '12',
'17','22'
]
Here is a less terse version
const array = ['3', '5', '6', '12', '14', '17', '22'],
rangeGap = 2,
arrRange = array.reduce((acc, num) => {
const range = acc.at(-1).split("-"), last = range.at(-1);
if ((num - last) <= rangeGap) {
if (range.length === 1) range.push(num); // new range
else range[range.length-1] = num; // rewrite last slot
acc[acc.length-1] = range.join("-"); // save the range
} else acc.push(num);
return acc;
}, [array[0]]); // initialise with first entry
console.log(arrRange)
You could define an offset of 2 and check the last pair if the delta is greater as this offset and push a new value the result set, otherwise take the value of the first part of the last stored or value and build a new pair.
const
array = ['3','5','6','12','17','22'],
offset = 2,
result = array.reduce((r, v, i, a) => {
if (!i || v - a[i - 1] > offset) r.push(v);
else r.push(`${r.pop().split('-', 1)[0]}-${v}`);
return r;
}, []);
console.log(result);
One possible generic, configurable and re-usable approach was to implement the reducer function as function statement where the initial value is an object of two properties threshold and result where the former defines a range value's tolerance or delta to its previous/next range value and the latter featuring all created/collected range values.
The reducer does process an array of just number values; therefore a map task which assures number values only has to be executed before.
function createAndCollectNumberRanges({ threshold, result }, current, idx, arr) {
threshold = Math.abs(threshold);
const previous = arr[idx - 1] ?? null;
const next = arr[idx + 1] ?? null;
if (
previous === null ||
previous < current - threshold
) {
result.push(String(current));
} else if (
(next > current + threshold || next === null) &&
previous >= current - threshold
) {
result.push(`${ result.pop() }-${ current }`);
}
return { threshold, result };
}
console.log(
['3', '5', '6', '12', '17', '22']
.map(Number)
.reduce(createAndCollectNumberRanges, {
threshold: 2,
result: [],
}).result
);
console.log(
['0', '3', '5', '6', '12', '14', '15', '17', '22', '23']
.map(Number)
.reduce(createAndCollectNumberRanges, {
threshold: 2,
result: [],
}).result
);
I would make this more generic in two ways. First, it probably improves readability to make 2 a parameter so that in other circumstances, you could choose 10 instead, or ten million.
Second, I would build this atop a more generally useful utility function that groups a sequence of values according to the result of a predicate applied pair-wise to the previous element and the most recent one. Here we would use the predicate (a, b) => b - a <= 2 (or really, given the above, (a, b) => b - a <= threshold.)
It could look like this:
const push = (x) => (xs) => ((xs .push (x)), xs)
const groupWhen = (pred) => (xs) =>
xs .reduce ((r, x, i, a) => i == 0 || !pred (a [i - 1], x)
? push ([x]) (r)
: ((push (x) (r .at (-1))), r),
[]
)
const groupCloseNumbers = (threshold) => (numbers) =>
groupWhen ((a, b) => b - a <= threshold) (numbers .sort ((a, b) => a - b))
.map (ns => ns .length == 1 ? ns [0] : `${ns .at (0)}-${ns .at (-1)}`)
console .log (groupCloseNumbers (2) (['3','5','6','12','17','22']))
//=> ["3-6", "12", "17", "22"]
push is a trivial helper, pushing the value onto an array, returning that array.
Our utility function is groupWhen; it continually adds values to the current group so long as our predicate returns true, creating a new group when it's false. Using this with our predicate, we'll get a result like [["3", "5", "6"], ["12"], ["17"], ["22"]].
Our main function, groupCloseNumbers, numerically sorts the values (if you know they're coming in already sorted, you can skip this step), calls groupWhen, and then maps over the results, to list single values as is and longer lists as ranges.
And if I was feeling that this was still not clear, I would look at several more helper functions, perhaps some or all of these:
const withinDistance = (threshold) => (a, b) => b - a <= threshold
const sortNumerically = (ns) => ns .sort ((a, b) => a - b)
const asRange = (ns) => `${ns .at (0)}-${ns .at (-1)}`
const valueOrRange = (ns) => ns .length == 1 ? ns [0] : asRange (ns)
const groupCloseNumbers = (threshold) => (numbers) =>
groupWhen (withinDistance (threshold)) (sortNumerically (numbers))
.map (valueOrRange)
But that's up to the developer and the developer's team, not me.

Find all permutations of 2 arrays in JS

I'm trying to find every permutations of 2 arrays like this:
// input
lowerWords = ['one', 'two', 'three' ]
upperWords = [ 'ONE', 'TWO', 'THREE' ]
// output
keywords = {
'one two three': true,
'ONE two three': true,
'ONE TWO three': true,
'ONE TWO THREE': true,
'ONE two THREE': true,
'one TWO three': true,
'one two THREE': true,
'one TWO THREE': true,
}
It should function with more than 3 items, both arrays will always be same length. This is my code:
const keywords = {}
const lowerWords = ['one', 'two', 'three' ]
const upperWords = [ 'ONE', 'TWO', 'THREE' ]
const wordCount = lowerWords.length
let currentWord = 0
let currentWords = [...upperWords]
while (currentWord < wordCount) {
currentWords[currentWord] = lowerWords[currentWord]
let keyword = currentWords.join(' ')
keywords[keyword] = true
currentWord++
}
currentWord = 0
currentWords = [...lowerWords]
while (currentWord < wordCount) {
currentWords[currentWord] = upperWords[currentWord]
let keyword = currentWords.join(' ')
keywords[keyword] = true
currentWord++
}
result is missing some
ONE TWO THREE: true
ONE TWO three: true
ONE two three: true
one TWO THREE: true
one two THREE: true
one two three: true
You could transpose the arrays for getting an array of pairs and then get all combinations of the pairs.
const
transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []),
combinations = array => array.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
var lowerWords = ['one', 'two', 'three'],
upperWords = ['ONE', 'TWO', 'THREE'],
pairs = transpose([lowerWords, upperWords]),
result = combinations(pairs);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Thought I'd give it a try. I used binary to get the possible combinations, as this problem needed a base 2 solution:
const low = ["one", "two", "three"];
const up = ["ONE", "TWO", "THREE"];
const words = [low, up]
const len = words[0].length
function getCombinations(noOfArrays, len) {
var temp, newCombo, combos = [];
for (var i = 0; i < (noOfArrays ** len); i++) {
temp = new Array(len).fill(0)
newCombo = i.toString(noOfArrays).split('');
newCombo.forEach((el, i) => temp[temp.length - newCombo.length + i] = +el);
combos.push(temp);
}
return combos;
}
function setCombinations(combos) {
return combos.map(combo => combo.map((el, i) => words[el][i]))
}
var combos = getCombinations(words.length, len)
combos = setCombinations(combos)
console.log(combos)
Explanation of loop:
1. temp = new Array(len).fill(0)
2. newCombo = i.toString(2).split("");
3. newCombo.forEach((el, i) => temp[temp.length - newCombo.length + i] = +el);
Creates temp array [0,0,0]
Grabs loop number (i) and converts it to binary e.g:
1 -> 1
2 -> 10
3 -> 11
4 -> 100
etc...
Then split the binary into an array 100 -> [1,0,0].
Then for each element push it in that new array. This gave a problem with pushing the 1 and 2 element arrays (10 -> [1,0]) into the back of the array. I used temp.length - newCombo.length + i to fix that.
That function then returns:
[ 0, 0, 0 ]
[ 0, 0, 1 ]
[ 0, 1, 0 ]
[ 0, 1, 1 ]
[ 1, 0, 0 ]
[ 1, 0, 1 ]
[ 1, 1, 0 ]
[ 1, 1, 1 ]
Then, I can map over each combination, and grab each array depending on the value, and get the words ('one' or 'ONE') via loop index.
Note this code works with more than one array, as long as the arrays are all the same length.
You need to get a total of 2 ^ 3 combinations. If you create a 2D matrix from the 2 arrays, the below table represents the row number from which of the item should be taken.
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
If you analyze indexes of the combinations, each of this a binary number from 0 to 2 ^ 3 with leading zeros.
So, you could
loop from 0 to 8
create binary number using toString(2)
Add leading zero using padStart
split each digit to get an array
Get each item from matrix[digit-from-binary][position-of-each-split]
join the array of items with a ' ' separator to get the key
Add the key to the output object
Working snippet:
function getAllCombinations(matrix) {
const combinations = 2 ** 3,
output = {};
for(let i = 0; i < combinations; i++) {
const key = i.toString(2)
.padStart(3, 0)
.split('')
.map((n, j) => matrix[n][j])
.join(" ")
output[key] = true;
}
return output
}
console.log(getAllCombinations([['one', 'two', 'three' ],[ 'ONE', 'TWO', 'THREE' ]]))
You can generalize this for m x n matrix. Instead of converting each to a binary number, you need to convert it to base-m and padStart to length n
function getAllCombinations(matrix) {
const rows = matrix.length,
columns = matrix[0].length,
combinations = rows ** columns;
return Array.from({ length: combinations },
(_, i) => i.toString(rows)
.padStart(columns, 0)
.split('')
.map((n, j) => matrix[n][j])
)
}
console.log(JSON.stringify(
getAllCombinations( [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ) // 3 x 3 matrix
));
console.log(JSON.stringify(
getAllCombinations( [[1, 2], [3, 4], [5, 6], [7, 8]] ) // 4 x 2 matrix
));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Following Code should work for you its recursive approach:
const lowerWords = ['one', 'two', 'three']
const upperWords = ['ONE', 'TWO', 'THREE']
let result = {};
function getCombinations(index, caseType, arr) {
if (index == 3) {
arr[index] = (caseType == 'lower' ? lowerWords : upperWords)[index];
result[arr.join(' ')] = true
return
}
arr[index] = (caseType == 'lower' ? lowerWords : upperWords)[index];
getCombinations(index + 1, 'lower', arr);
getCombinations(index + 1, 'upper', arr);
}
getCombinations(0, 'lower', [])
getCombinations(0, 'upper', [])
console.log('resultresult', result)
Here's a generalised version based on my other answer that handles a variable number of input arrays with varying lengths:
const g = (arrs, i=0, comb=[]) =>
!arrs.some(arr => i < arr.length)
? [comb]
: arrs.reduce((acc, arr) =>
i >= arr.length ? acc :
acc.concat(g(arrs, i + 1, comb.slice().concat(arr[i])))
, [])
// Example output
let input = [['ONE','TWO','THREE'], ['one','two'], [1,2,3,4]]
let str = ''
for (let line of g(input))
str += JSON.stringify(line) + '\n'
console.log(str)
We can enumerate these directly. Here's one algorithm:
If we've reached the end of
the list, return the combination
the current recursion branch is
building.
Otherwise, create a new branch
that picks the next item from B,
while the current branch picks
the next item from A.
JavaScript code:
function f(A, B, i=0, comb=[]){
return i == A.length
? [comb]
: f(A, B, i + 1, comb.concat(A[i])).concat(
f(A, B, i + 1, comb.slice().concat(B[i])))
}
console.log(JSON.stringify(f(['one','two','three'], ['ONE','TWO','THREE'])))

Split an array with custom function

i would like to split an array into multiple chunks but applying a function to it to decide how to create the chunks.
For example, if i have an array of letters, numbers or letters and numbers, apply a function to the array to split it into array of arrays of the previous categories.
let arr = ['a', 'b', '1', '2', 'a1', 'a2', 'c', '3', 'a3']
myChunkFunction(arr, myCustomSplitFunction)
// result
[['a','b','c'], ['1','2','3'], ['a1', 'a2','a3']]
Lodash has a chunk function but it splits into n chunks, also array has a slice function but you need to specify the begin and the end so how could i split with a custom function.
Try doing this
let arr = ['a', 'b', '1', '2', 'a1', 'a2', 'c', '3', 'a3']
const splitFn = (str) => Number.isInteger(+str) ? 0 : str.length == 1 ? 1 : 2
const myChunkFunction = (arr, fn) => arr.reduce((r,c) => {
let t = fn(c)
r[t] = [...r[t], c]
return r
}, [[],[],[]])
console.log(myChunkFunction(arr, splitFn))
Hint
The key to the answer is to, somehow, reorganize the source array such that all the elements with the same key will be in the same place.
The easiest way I can think to solve it is by using hash-map. Each element in the hash-map will be a different array containing all the elements with the same key.
Try it for your self before you keep reading and see the full solution.
The implementation
As you can see, I solved it as functional as possible. To avoid mutations, I used reduce to iterate over the source array and put each element in the hashmap (by generating a key from the element).
I recreate the final hash-map over and over using shallow copy. Finally, I convert the hash-map to an array of array (because that was your demand) using Object.values
const splitArrayByKey = extractKey => array => {
const arraysByKey_obj = array.reduce((hashMapOfArrays,element)=> {
const key = extractKey(element);
// if we already added an element with the same key,
// then we add the current element to there.
// else, we create a new key and put the current element there.
if(hashMapOfArrays.hasOwnProperty(key))
return {
...hashMapOfArrays,
[key]: [...hashMapOfArrays[key],element]
};
return {
...hashMapOfArrays,
[key]: [element]
};
},{});
// transform the arraysByKey_obj to an array of arrays:
return Object.values(arraysByKey_obj);
};
// example 1:
const result1 = splitArrayByKey(element=>element)([1,2,3,1,2,3]);
console.log(result1);
console.log('------------------');
// example 2:
const result2 = splitArrayByKey(element=>element.id)([{id:1,x:1},{id:{},x:2},{id:"id",x:3},{id:1,x:4}]);
console.log(result2);
Here is a way to do this via ES6:
let arr = ['a', 'b', '1', '2', 'a1', 'a2', 'c', '3', 'a3']
const splitFn = (str) => Number.isInteger(+str) ? 0 : str.length == 1 ? 1 : 2
const myChunkFunction = (arr, fn) => arr.reduce((r,c) => {
let t = fn(c)
r[t] = [...r[t], c]
return r
}, [[],[],[]])
console.log(myChunkFunction(arr, splitFn))
The typeFn plays the role of filtering the elements to number, string with 1 length and other. That output is used by the myChunkFunction to place the element in the right array.
You could do something like this with less control and in one line with reduce and ES6 array spread:
let arr = ['a', 'b', '1', '2', 'a1', 'a2', 'c', '3', 'a3']
const result = arr.reduce((r,c) =>
(Number.isInteger(+c) ? r[0] = [...r[0], c] :
c.length == 1 ? r[1] = [...r[1], c] : r[2] = [...r[2], c], r), [[],[],[]])
console.log(result)
You start with [[],[],[]] and fill each of the sub arrays based on number, length of the string == 1, other lenghts.
You could wrap that in a function.
const arr = ['a', 'b', '1', '2', 'a1', 'a2', 'c', '3', 'a3'];
const getClassification = function(x){
const hasNumber = x.split('').some(x => parseFloat(x));
const hasChar = x.split('').some(x => !parseFloat(x));
if(!parseFloat(x) && (!hasNumber && hasChar)) return 0;
else if(parseFloat(x)) return 1;
else return 2;
}
const myChunkFunction = function(arr, classifier){
let jaggedArray = [[], [], []];
arr.forEach(x => {
jaggedArray[classifier(x)].push(x);
})
return jaggedArray;
}
console.log(myChunkFunction(arr, getClassification));
I think this satisfies.

Join array without removing whitespace

I have a little question about joining arrays. I have an array of letters, something like that:
let array = ['a','b','','c']
I wan't to join elements in array to have output like that:
let array = ['ab','c']
Can you help me? I was searching but everything i found was about removing whitespaces from arrays or string :(
Something along these lines:
let array = ['a', 'b', '', 'c'];
let res = array.reduce((res, s) => {
if (s.length) {
res[res.length - 1] += s;
} else {
res.push('');
}
return res;
}, ['']);
console.log(res);
It does make the assumption that there will be at least one string in the array, that the last element won't be an empty string and that there won't be two adjacent empty strings. Adjust as necessary if those are concerns.
You can use a combination of Array#map, Array#join and String#Split to achieve what you want.
Here, I used a space as the delimiter, but you can use anything that you don't use in your array.
let array = ['a','b','','c'];
let result = array.map(e => e.length ? e : ' ').join('').split(' ');
console.log(result);
You could use reduce() method to create new array and one variable to increment on empty string.
let array = ['a', 'b', '', 'c', 'd', 'e', '', '', '', 'f', '', 'g'];
let i = 0;
let result = array.reduce((r, e, j, arr) => {
r[i] = (r[i] || '') + e;
if (!e && arr[j - 1]) i++
return r;
}, [])
console.log(result)

Count number of overlapping items in two arrays

I have two arrays, of which the Remove should be removed from the List, and the number removed should be counted.
I'm using this to remove those from 'Remove':
let output=this.List.filter((el)=>{
return this.Remove.indexOf(el) <0;
})
This is working fine, but I'd like to count the number of items that have overlapped, and thus been removed in the filter.
For example, if List=['1','2','3','4','5'] and Remove=['1','4'], count would be 2.
Many thanks!
You can do this in one reduce run. For example, you can create an object and track both cleared array and a number of deleted elements:
const a = [1,2,3,4,5,6,7];
const b = [1,4,9]; // elements to remove
const c = a.reduce((acc, cur) => b.includes(cur)
? Object.assign(acc, {n: acc.n + 1})
: Object.assign(acc, {res: acc.res.concat(cur)}), { n: 0, res: []});
console.log(c)
If you don't want to compare lengths as suggested in comments (which is fine...), I can propose you the following solution using a counter:
let list = ['1', '2', '3', '4', '5'],
remove = ['1', '4'],
overlap = 0;
let output = list.filter(el => {
let bool = remove.indexOf(el) < 0;
if (!bool) overlap++;
return bool;
});
console.log(overlap);
Assuming all the items in the arrays a and b are unique, you can make use of Sets to get the absolute number of intersections as a one-liner:
const a = [1,2,3,4];
const b = [2,4,5];
const overlap = a.length + b.length - new Set(a.concat(b)).size; // => 2

Categories

Resources