Count number of overlapping items in two arrays - javascript

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

Related

How to delete duplicate copies on mapping element counter?

I am trying to create a little project where I can count the number of elements in an array. This part I have already done. My question is how can I fix a counting problem? I'm using a mapping method to get my element occurrence counter, but I want to put the data into an array. The only way I know how is to take the data from the mapping with .get. They way I'm doing it is by using this first part here:
let winners = [1, 2, 3, 2];
let nullArray = [];
let mode = new Map([...new Set(winners)].map(
k => [k, winners.filter(t => t === k).length]
));
for (let n in winners) {
nullArray.push(mode.get(winners[n]));
console.log(nullArray);
}
However, this will *n push matches. Like, if you have l=[1,2,2], because l[1]=2, and l[2]=2, they will be pushed into the nullArray as 2, however, it will also push l[2] and l[1] as the values are different. If you have three matches, it would duplicate 3 times, and so on. To counter this, I tried making a detector that would calculate when the same numbers in the nullArray are from the same copy but in a different order. To do this, I used the code I have below (combined with the original code)
let winners = [1, 2, 3, 2];
let nullArray = [];
let mode = new Map([...new Set(winners)].map(
k => [k, winners.filter(t => t === k).length]
));
for (let n in winners) {
nullArray.push(mode.get(winners[n]));
console.log(nullArray);
}
for (let num in nullArray) {
for (let c in nullArray) {
if (nullArray[num] === nullArray[c] && winners[num] === winners[c]) {
nullArray.splice(num, 1);
}
console.log(nullArray);
}
}
However, whenever I try this, the specific output on this array is [2,2]. How could I make a general solution that will eliminate all duplicate copies, only leaving a single copy of the number count (which in the case of [1,2,3,2], I would want nullArray=[1,2,1] as an output)
You can do something like this.
If you don't care about the order, you can just do the following.
const remove_dup_and_count = (winners = [1, 2, 3, 2]) =>{
let map = {}
//count elements
for (let n in winners) {
const curr_val = winners[n]
//duplicate, so we increment count
if(curr_val in map) map[curr_val] += 1
//first seen, so we start at 1
else map[curr_val] = 1
}
//lets grab the array of all keys in map
const keys_arr = Object.keys(map)
let count_arr = []
for(let i of keys_arr){
count_arr.push(map[i])
}
return count_arr
}
console.log(remove_dup_and_count())
If you care about the order, this is your best bet:
const remove_dup_and_count = (winners = [1, 2, 3, 2]) =>{
let map = new Map()
//count elements
for (let n in winners) {
const curr_val = winners[n]
//duplicate, so we increment count
if(map.get(curr_val)) map.set(curr_val, map.get(curr_val) + 1)
//first seen, so we start at 1
else map.set(curr_val,1)
}
let count_arr = []
//lets grab the array of all keys in map
for (const [key, value] of map) {
count_arr.push(value)
}
return count_arr
}
console.log(remove_dup_and_count())
I think you can use .reduce() method and then retrieve how many times the value is repeated in array using map.values(); something like the following snippet:
const winners = [1, 2, 3, 2];
const mapWinners = winners.reduce((winnersAccumulator, singleWinner) => winnersAccumulator.set(singleWinner, (winnersAccumulator.get(singleWinner) || 0) + 1), new Map())
console.log([...mapWinners.values()])

Find second largest elements in array with duplicates in javascript

Am having array to find second largest elements including repeated value pls find below example.
const arr= [1,2,5,5,6]
expected result should be
[5,5]
I tried with map and math.max but i stuck up on logical issue.kindly help me
Below snippet could help you
const arr = [1, 2, 5, 5, 6]
const max = Math.max(...arr)
const newArr = arr.filter(element => element !== max)
const newMax = Math.max(...newArr)
const secondLargest = arr.filter(element => element === newMax)
console.log(secondLargest)
Here is a simpler approach, However it may not be the best approach in terms of performance for large data
const ar= [1,2,5,5,6]
secmax = Math.max(...ar.filter((n,i) => Math.max(...ar) !=n ))
res = ar.filter(n =>n == secmax)
console.log(res)
Using a Set to extract unique values shortens the code quite a bit
var arr = [1,5,2,5,4,8];
var uniqueValues = [...new Set(arr)].sort((a, b) => b-a);
var secondHighest = uniqueValues[1]; // 0 is max, 1 is second highest, etc.
var result = arr.filter(x => x === secondHighest);
Please keep in mind that there should be some due diligence in accessing the results (what happens if the code is fed with empty arrays, or arrays with a single repeated value? There are many cases not covered here)
You could group the values and sort the array of arrays and get the second array.
const
array = [1, 2, 5, 5, 6],
result = Object
.values(array.reduce((r, v) => (r[v] = [...(r[v] || []), v], r), {}))
.sort(([a], [b]) => b - a)
[1];
console.log(result);

Join an array by commas and "and"

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

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.

How to sort a 2D array based on the sum of each row?

Excuse me, a quick question:
I have a 2D array of elements where each element has an int value encapsulated. Something like this:
MyComponent = {... , value: 2, ...} // each instance has a value property
MyArray = [
[Component1, Component2],
[Component3],
[Component4, Component5, Component6, Component7],
... ]
I would like to sort my 2D array rows, based on the sum of value of elements in each row.
A little detailed: I have groups of choices (combinations) for the user to select, where each single choice in any group has a certain value. I would like to show the combinations that have the highest sum or total first (so it is descending).
What is a quick and efficient way to achieve that in javascript?
var originalArray = [
[{value: 2}, {value: 3}],
[{value: 11}],
[{value: 1}, {value: 2}, {value: 3}, {value: 4}]
];
var sortedArray = originalArray.sort(function(row1, row2) {
add = function(a, b) {return a + b.value};
return row1.reduce(add, 0) - row2.reduce(add, 0);
});
console.log(sortedArray);
Let's break this down, shall we?
We start with the original array:
var originalArray = [
[{value: 2}, {value: 3}],
[{value: 11}],
[{value: 1}, {value: 2}, {value: 3}, {value: 4}]
];
Then we sort it. Let's work from the inside out. First, how do we calculate the sum of a row? We can use the reduce function:
exampleRow = [{value: 2}, {value: 3}];
add = function(a, b) {return a + b.value};
console.log(exampleRow.reduce(add, 0));
The reduce function takes in another function (add in this case) and uses it to iterate over an array and reduce it to a single item (usually a number). reduce needs a function that takes in two arguments - the running total, and the next item. Reduce roughly does the following:
array.prototype.reduce = function(fn, initial) {
runningTotal = initial;
for (var i = 0; i <= array.length; i++) {
runningTotal = fn(runningTotal, array[i]);
}
return runningTotal;
}
In words, it starts with the initial value, and then runs your function over and over, with each run using the last run's output and the next item in the array.
In our case, the fn function is add:
add = function(a, b) {return a + b.value};
add is very simple; it takes the running total (a) and adds the value of the next item (b). Over the entire array, this simplifies to 0 + array[0].value + array[1].value + ... + array[array.length-1].value - the sum of the array.
We're almost there! The last part is the actual sorting. We do this with the sort function (surprise!). The sort function also takes in a function with two arguments that it uses to iterate over the array. The function you give to sort, however, has to return a number. sort uses this function to compare the two arguments; if the output is positive, the first item is 'bigger', while if the output is negative, the second item is 'bigger'. (If the output is zero they are equal.) sort uses this to sort the array in ascending order.
In our case, our function takes in the two rows and returns the sum of the first minus the sum of the other. This means that if the first row's sum is greater ist will be later in the array and vice versa.
The following sorting function precomputes row-sums to avoid redundant summations and stores the sums in a Map for fast retrieval within the array.sort() callback:
// Sort a 2D array in-place by the sum of its rows:
function sortByRowSum(array) {
let sums = new Map(array.map(row =>
[row, row.reduce((sum, element) => sum + element, 0)]
));
return array.sort((a, b) => sums.get(a) - sums.get(b));
}
// Example:
console.log(sortByRowSum([[1, 3], [2], [0, 1, 8], [0]]));
Since your row elements aren't simple numbers as in the example above, you would need to adapt the reduce callback to the structure of your row elements, e.g.:
let sums = new Map(array.map(row =>
[row, row.reduce((sum, component)=> sum + component.value, 0)]
));
Comparing performance of 2 solutions for myself, might be useful for others, feel free to optimize more: https://jsperf.com/sort-array-by-sum-of-rows/1
I got only 30-70% improvement in Firefox, Chrome and Edge (my ES6 code does not compile in IE11), so the reduced readability is not worth it IMHO:
const simpleSorter = (arr) => [...arr].sort(
(a, b) => b.reduce((sum, i) => sum + i.value, 0) -
a.reduce((sum, i) => sum + i.value, 0)
)
const fasterSorter = (arr) => {
// note: should use stable sort for equal row scores
const rowScores = arr.map((row, index) => ({
score: row.reduce((sum, i, index) => sum + i.value, 0),
index
})).sort((a, b) => b.score - a.score)
return rowScores.map(s => arr[s.index])
}
const rand = () => Math.floor((Math.random() * 10)) + 1
const idGenerator = (() => {
let i = 0
return () => i++
})()
const componentGenerator = () => ({a: 'x', value: rand(), id: idGenerator()})
const rowGenerator = () => [...Array(rand())].map(componentGenerator)
const myArray = [...Array(10)].map(rowGenerator)
const prettyPrinter = (arr) => arr.map(i => {
const temp = i.map(j => j.value)
return temp.join(' + ') + ' = ' + temp.reduce((a, b) => a + b)
})
const myArraySimpleSorted = simpleSorter(myArray)
const myArrayFasterSorted = fasterSorter(myArray)
console.log('test',
prettyPrinter(myArraySimpleSorted).toString() ===
prettyPrinter(myArrayFasterSorted).toString())
console.log('myArray', prettyPrinter(myArray))
console.log('myArraySimpleSorted', prettyPrinter(myArraySimpleSorted))
console.log('myArrayFasterSorted', prettyPrinter(myArrayFasterSorted))
console.log('first in myArray', myArray[0][0])
console.log('first in myArraySimpleSorted', myArraySimpleSorted[0][0])

Categories

Resources