How to delete duplicate copies on mapping element counter? - javascript

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()])

Related

Creating an array from another array

I'm learning Javascript and I'm wondering what the most elegant way to convert this: [1,8]
into this:[1,2,3,4,5,6,7,8]?
Thanks a lot!
const argarray = [1, 8]
const countToN = (array) => {
// init results array
let res = []
// start at the first value array[0] go *up to* the second array[1]
for (let i = array[0]; i <= array[1]; i++) {
res.push(i)
}
// return the result
return res
}
console.log(countToN([1, 10]))
This would accommodate what you're trying to do, but it's fairly brittle. You'd have to check that it's an array and that it has only 2 values. If you had other requirements, I could amend this to account for it.
Here's a solution without loops. Note that this only works with positive numbers. It supports arrays of any length, but will always base the result off of the first and last values.
const case1 = [1, 8];
const case2 = [5, 20];
const startToEnd = (array) => {
const last = array[array.length - 1];
const newArray = [...Array(last + 1).keys()];
return newArray.slice(array[0], last + 1);
};
console.log(startToEnd(case1));
console.log(startToEnd(case2));
Here's a solution that works for negative values as well:
const case1 = [-5, 30];
const case2 = [-20, -10];
const case3 = [9, 14];
const startToEndSolid = (array) => {
const length = array[array.length - 1] - array[0] + 1;
if (length < 0) throw new Error('Last value must be greater than the first value.');
return Array.from(Array(length)).map((_, i) => array[0] + i);
};
console.log(startToEndSolid(case1));
console.log(startToEndSolid(case2));
console.log(startToEndSolid(case3));
A simple for loop will do it. Here's an example that has error checking and allows you to range both backwards and forwards (ie [1, 8], and also [1, -8]).
function range(arr) {
// Check if the argument (if there is one) is
// an array, and if it's an array it has a length of
// of two. If not return an error message.
if (!Array.isArray(arr) || arr.length !== 2) {
return 'Not possible';
}
// Deconstruct the first and last elements
// from the array
const [ first, last ] = arr;
// Create a new array to capture the range
const out = [];
// If the last integer is greater than the first
// integer walk the loop forwards
if (last > first) {
for (let i = first; i <= last; i++) {
out.push(i);
}
// Otherwise walk the loop backwards
} else {
for (let i = first; i >= last; i--) {
out.push(i);
}
}
// Finally return the array
return out;
}
console.log(range([1, 8]));
console.log(range('18'));
console.log(range());
console.log(range([1]));
console.log(range([-3, 6]));
console.log(range([9, 16, 23]));
console.log(range([4, -4]));
console.log(range([1, -8, 12]));
console.log(range(null));
console.log(range(undefined));
console.log(range([4, 4]));
Additional documentation
Destructuring assignment
Use Array#map as follows:
const input = [1,8],
output = [...Array(input[1] - input[0] + 1)]
.map((_,i) => input[0] + i);
console.log( output );

Reverse an array without reverse using shift, confusing result

I'm trying to reverse an array without using reverse() in javascript. There are many solutions to this problem on here, but I'm just curious to find out why mine doesn't work, because I can't figure out why I don't get correct results.
const reverse = (arr) => {
const result = []
let slice = 0
for (let i = 0; i < arr.length; i++) {
slice = arr.shift()
result.unshift(slice)
}
return result
}
console.log(reverse([1, 2, 3, 4]))
// output --> [2, 1]
You could always use arr.forEach. Something like:
const reverse = (arr) => {
let newArr = [];
arr.forEach(el => newArr.unshift(el));
return newArr;
}
console.log(reverse([1, 2, 3, 4]))
This leaves the original array untouched, and just performs the action on each element.
I understand you seem to want to use shift and unshift, and it's possible, but if you do that, use a while loop, something like:
const reverse = (arr) => {
const result = []
let slice = 0
while ( arr.length > 0) {
slice = arr.shift()
result.unshift(slice)
}
return result
}
console.log(reverse([1, 2, 3, 4]))

Optimizing a function that returns all combinations of an array's elements

I am trying to optimize this function that I found online.
Example input: [1, 2, 3]
Example output: [[1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
Here's the code:
const combinations = arr => {
let parentValue = [];
let cursor = 0;
for (const parentThis of arr) {
const value = [[parentThis]];
for (const thiss of parentValue) {
value.push(thiss.concat([parentThis]));
}
parentValue = parentValue.concat(value);
}
return parentValue;
};
(the variable names are weird because I'm running this as a MongoDB aggregation)
Running this 10k times on an array of 10 elements takes about 23 seconds. How can I make it run faster? I am open to some tradeoffs.
An obvious improvement I found is to decrease the amount of elements. Running it 10k times on an array of 9 elements takes 9 seconds.
I suspect another improvement would be to reject some outputs before they are generated (single-element combinations and the all-elements combination might not be too useful), but I can't figure out how to put this into code.
I tried to improve by removing the first iteration (supplying [arr[0]] as the initialValue and starting the outer for loop from the second element) but it doesn't seem to make a significant difference.
About 1 second for 10k times on an array of 10 elements.
function powerset(A){
const n = A.length;
const numSets = (1 << n) - 1;
const result = new Array(numSets);
for (let k=1; k<=numSets; k++){
const set = [];
result[k - 1] = set;
let temp = k;
let i = 0;
while (temp){
if (temp & 1)
set.push(A[i]);
temp >>= 1;
i += 1;
}
}
return result;
}
var A = [1,2,3,4,5,6,7,8,9,10];
var start = new Date;
for (let i=0; i<10000; i++)
var t = powerset(A);
console.log((new Date - start) / 1000);
As noted by georg in the comments under this answer, the line, const result = new Array(numSets); can be replaced with const result = []; and the assignment to result.push(set); in order to possibly see further improvement in efficiency. (Tested on node with array size 15-20.)
Maybe a simple push could speed the code a bit.
// parentValue = parentValue.concat(value);
parentValue.push(...value);

Split array into arrays of numbers where the sum is equal to a specific target

I need to create a function that take as parameter an array and a target. It should return an array of arrays where the sum of these numbers equals to the target
sumPairs(array, target) {
}
For example:
sumPairs([1, 2, 3, 4, 5], 7) // output : [[2, 5], [3, 4]]
I know I have to use map(), and probably reduce(), set(), or filter() maybe (I read their documentation in MDN but still cant find out). I tried some ways but I can't get it.
If you guys could help me to find out how to dynamically create arrays and push them into a new array..
I read there some solutions (Split array into arrays of matching values) but I hate to just use created functions without knowing what they really do or how they work.
Some very basic code for achieving it, Just run all over combinations and conditionally add the items you want.
function sumPairs(array, target) {
var res = [];
for(var i = 0; i < array.length; i++){
for(var j = 0; j < array.length; j++){
if(i!=j && array[i]+array[j]==target &&
res.filter((x)=> x[0] == array[j] && x[1] == array[i]).length == 0 )
res.push([array[i], array[j]]);
}
}
return res;
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
Option 2 - see this answer for more options (like using reduce)
function sumPairs(array, target) {
return array.flatMap(
(v, i) => array.slice(i+1).filter(w => (v!=w && v+w==target)).map(w=> [w,v])
);
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
"The exercise says that it sould be arrays of pairs that sum the
target value so I think only 2 items"
If you need a pair that matches a sum and you pick any number from the list, you are left with
the following equation to solve num + x = sum where we want to find x. E.g. if you picked 7 and the target sum is 10 then you know you are looking for a 3.
Therefore, we can first construct a counting map of the numbers available in our list linear (O(n)) time and then search for matches in linear time as well rather than brute forcing with a quadratic algorithm.
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
return nums.reduce((pairs, num) => {
countByNum[num]--;
const target = sum - num;
if (countByNum[target] > 0) {
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[num]++;
}
return pairs;
}, []);
}
function countGroupByNum(nums) {
return nums.reduce((acc, n) => (acc[n] = (acc[n] || 0) + 1, acc), {});
}
Here's another implementation with more standard paradigms (e.g. no reduce):
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
const pairs = [];
for (const num of nums) {
const target = sum - num; //Calculate the target to make the sum
countByNum[num]--; //Make sure we dont pick the same num instance
if (countByNum[target] > 0) { //If we found the target
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[target]++; //Didin't find a match, return the deducted num
}
}
return pairs;
}
function countGroupByNum(nums) {
const countByNum = {};
for (const num of nums) {
countByNum[num] = (countByNum[num] || 0) + 1;
}
return countByNum;
}
You can also sort your array and find all the pairs with given sum by using two pointer method. Place the first pointer to the start of the array and the second pointer to the end.
if the sum of the values at the two places is :
More than target: Decrement your second pointer by 1
Less than target: Increment your first pointer by 1
Equal to target: This is one possible answer, push them to your answer array and increment your first pointer by 1 and decrement your second pointer by 1.
This is more performant solution with complexity O(n*log(n))

How to check if array element is the only one of its value in array?

I've seen many similar questions and answers here but none that directly answered this question. For each array element I'm looking for a way (with JavaScript) to check if it's the only one of its kind in the array, or if there is at least one other of it. For example:
const arr = [1,2,2]
looking for something that will return
true, false, false
when looping through arr.
const arr = [1, 2, 2];
console.log(arr.map(item => arr.indexOf(item) === arr.lastIndexOf(item)));
const arr = [1, 2, 2];
arr.map(item => arr.indexOf(item) === arr.lastIndexOf(item));
You can do it in two passes:
build a Map containing the count of each element
look up each element in that Map
Like so:
const getCounts = iterable => {
const counts = new Map();
for (const x of iterable) {
counts.set(x, (counts.get(x) ?? 0) + 1); // use || for ES6 compat
}
return counts;
};
const arrCounts = getCounts(arr);
arr.map(x => arrCounts.get(x) === 1)

Categories

Resources