breaking an array into subsets - javascript

I am trying to get this function to split an array into subsets. each subset is to have numbers that are equal to the previous or within 1 from the previous number.
The example I have below should return two subsets but it returns {0, 1, 2, 3} instead. Any idea on what I am doing wrong? Also, is there a better way to dynamically create an array for each new subset? Thanks
function max_tickets() {
var arr = [4, 13, 2, 3];
var myarr = arr.sort(function(a, b){return a-b});
for(var i = 0; i<myarr.length; i++){
var iplus = i+1;
if(i === i || i === iplus){
newArr= [];
newArr.push(i);
}else if (i !== i || i !== iplus){
arr2 =[];
arr2.push(i);
}
}
}

What you are trying to do is usually called "partitioning". The generic version of the problem is to partition an array into sub-arrays using some "rule", or predicate, or condition, which specifies which partition a particular element is supposed to go into, or specifies that it should go into a new partition.
The pseudo code for doing this would be:
To partition an array:
Initialize the resulting array
For each element in the array
If that element starts a new chunk
Create a new empty chunk in the resulting array
Add the element to the most recent chunk
Return the result
This can be expressed in JS quite straightforwardly as
function partition(array, fn) {
return array.reduce((result, elt, i, a) => {
if (!i || !fn(elt, i, a)) result.push([]);
result[result.length - 1].push(elt);
return result;
}, []);
}
Now we need to write the function saying when a new partition should start:
// Is the element within one of the previous element?
function close(e, i, a) {
return Math.abs(e - a[i-1]) > 1;
}
We can now partition the array with
partition([[4, 13, 2, 3], close)

This should work.
function max_tickets() {
var arr = [4, 13, 2, 3];
var myarr = arr.sort(function (a, b) { return a - b });
arrSubsets = [];
arr1 = [];
for (var i = 0; i < myarr.length; i++) {
if (myarr[i - 1] === undefined) {
arr1.push(myarr[i]);
continue;
}
if (myarr[i] - myarr[i - 1] <= 1) {
arr1.push(myarr[i]);
}
else {
arrSubsets.push(arr1);
arr1 = [];
arr1.push(myarr[i]);
}
}
if (arr1.length > 0)
arrSubsets.push(arr1);
}
max_tickets();

Based on your questions:
Any idea on what I am doing wrong?.
Inside of your loop you are using i as if it is the value of the array, but the loop goes from 0 to the value of myarr.length in your particular case 4, so that makes the value of i to be 0, 1, 2, 3.
As you can see you are using the values of the index to compare, instead of using the values of the array in order to use the values of the array you must specify the arrayname[index], in your case myarr[i] that will give you the values: 4, 13, 2, 3.
Also, is there a better way to dynamically create an array for each new subset?
Yes you can create an array inside of another array dynamically inside of a loop:
var b = [];
for(var i = 0; i < 10; i++){
b.push(['I am' + i, i]);
}
As you can see in the previous example I'm creating an array inside of the b array so once the loop finishes the b array will have 10 arrays inside of it with 2 elements each.

Related

Sort 2D array by matching the last values with the first one in the next index

So basically, I have this array
array = [[1,0],[2,1],[0,3],[3,2]]
Is there any quick method to convert the array to look like this
array = [[0,3],[3,2],[2,1],[1,0]]
What I want is for the first element of the new array to be always contain 0 in the first spot of the nested array. That's easy enough, because the sort() function does exactly that; the hard part is ordering the new array like the one above.
In the most simplest terms, I'd like for nested arrays to be "connected": see how the 3 of the first nested array matches the other 3 to its right and so on.
Feel free to leave any comments so I can try to explain the problem a little bit better.
For the example you gave the easiest solution would be to sort the 2d array by the second element of each inner array, as follow:
let array = [[1,0],[2,1],[0,3],[3,2]];
array.sort((a, b) => b[1] - a[1]);
In that way you can sort the array with the sort method according to the elements in the inner arrays.
let array = [[1,0],[2,1],[0,3],[3,2]];
array.sort((a, b) => b[1] - a[1]);
console.log(array);
You could take an object as reference and rebuild the array by taking the chained items.
const
getItems = (reference, value) => {
const a = reference[value];
return a ? [a, ...(a[1] === 0 ? [] : getItems(reference, a[1]))] : [];
},
array = [[1, 0], [2, 1], [0, 3], [3, 2]],
reference = array.reduce((r, a) => (r[a[0]] = a, r), {}),
result = getItems(reference, 0);
console.log(result);
Look at this method:
const array = [[1,0],[2,1],[0,3],[3,2]];
const result = [...array];
// Put the subarray with the zero element first
// You can use the sort() function but I guess this method
// performs better in terms of time
for (let i = 0; i < result.length; ++i) {
if (result[i][0] === 0) {
result.unshift(...result.splice(i, 1));
break;
}
}
// Now reorder the array so that the last index of a subarray
// matches with the first index of the other subarray
for (let i = 0; i < result.length; ++i) {
for (let j = i + 1; j < result.length; ++j) {
if (result[i][1] === result[j][0]) {
// recollocate the subarray so that it matches the way we want
result.splice(i + 1, 0, ...result.splice(j, 1));
break;
}
}
}
console.log(result)

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 get the sum of all duplicates in an array?

I am trying to calculate the sum of all duplicates in an array. For example:
duplicate([1,1,2,3,3]) --> should return 8.
I have written the following function to calculate the sum of duplicates in an array using JavaScript. Currently it is returning an array with duplicates one less than what they are present in the array.
function duplicate(arr) {
var sum = 0;
arr.sort();
var stack = [];
for(var i = 0; i < arr.length; i++){
if (arr[i] === arr[i+1]) {
stack.push(arr[i])
}
}
return stack;
}
console.log(duplicate([1,2,1,2,2,3,3]))
This is returning [ 1, 2, 2, 3 ]
How do I get the correct array and calculate the correct sum? I have to use Object for that?
To make the logic easier, you might filter out the non-duplicates by checking whether their indexOf in the array is equal to their lastIndexOf in the array:
function duplicate(arr) {
const duplicates = arr.filter(elm => arr.indexOf(elm) !== arr.lastIndexOf(elm));
return duplicates.reduce((a, b) => a + b);
}
console.log(duplicate([1,1,2,3,3])); // --> should return 8.
console.log(duplicate([1,2,1,2,2,3,3]));
Initially create an object where the keys will be the integer and their value will be the number of occurrence. Then if the number of occurrence is more than 1 , multiply the number with number of occurrence.
function duplicate(arr) {
let dupVal = 0;
let k = arr.reduce((acc, curr, index) => {
if (acc[curr] === undefined) {
acc[curr] = 1
} else {
acc[curr] += 1;
}
return acc
}, {});
for (let keys in k) {
if (k[keys] > 1) {
dupVal += parseInt(keys, 10) * k[keys]
}
}
return dupVal;
}
console.log(duplicate([1, 2, 1, 2, 2, 3, 3]))
Try This one
const arr = [1,1,2,3,3]
let dup = arr.filter((value, index)=>{
// creating a copy of main array
let copyarr = [].concat(arr)
// removing present value
copyarr.splice(index,1)
// after removing present value, if you still
// get the value in copied array that means
// it has duplicates
if(copyarr.indexOf(value)>-1){
return true
}
return false
})
// now add it using reduce
let sum = dup.reduce((acc, value)=> acc+value,0)
console.log(sum)
Copy above code and paste into chrome devTool. You will get the answer.
The problem is that you are matching value with immediate next value in array, in array that is sorted already it will work, but not on unsorted one. So try to sort the array first and then run your code.
Edit :
Looks like sorting is added in code,
But another condition => if there is number that is repeated more than twice it should be handled and only appear once in stack, if that is required.
This will : console.log(duplicate([1,2,1,2,2,3,3]))
Result this : [1,2,3]
function duplicate(arr) {
var sum = 0;
arr.sort();
var stack = [];
for(var i = 0; i < arr.length; i++){
if (arr[i] === arr[i+1]) {
if(stack.length == 0 || (arr[i] != stack[stack.length-1])){
stack.push(arr[i])
}
}
}
return stack;
}
you can use JS Array.reduce method to accomplish your requirement in a shorter way
function sumDplicates(arr) {
return arr.reduce(function(tot, val, index, _arr) {
if (_arr.lastIndexOf(val) > index || _arr.indexOf(val) != index)
return tot + val;
return tot
}, 0)
}
console.log(sumDplicates([1, 1, 2, 3, 3]));
console.log(sumDplicates([1, 2, 1, 2, 2, 3, 3]));
You can pursue your original sorting approach with a slight modification:
if (arr[i] === arr[i + 1] || arr[i] === arr[i - 1])
That is, check if the previous or the next element in the sorted array is equal to the current element for it to qualify as a duplicate.
The following solution accomplishes this with filter and reduce:
function duplicate(array) {
return array
.sort((a, b) => a - b)
.filter((a, i, arr) => (arr[i] === arr[i + 1] || arr[i] === arr[i - 1]))
.reduce((a, b) => a + b, 0);
}
console.log(duplicate([1, 1, 2, 3, 3]));
console.log(duplicate([1, 2, 1, 2, 3, 3]));
Array.reduce() and Array.lastIndexOf() will simply solve your problem.
function sum(arr)
{
return arr.reduce(function(sum, item){
return arr.lastIndexOf(item)!==arr.indexOf(item) ? sum+=item : sum;
},0)
}
console.log(sum([1,1,2,3,3]));
console.log(sum([1,2,3,4]));
console.log(sum([1,2,2,3,4]));
console.log(sum([1,1,2,2,3,3,4,4]));
Though I don't know much about JavaScript, If I were you, I would have simply kept a temporary array, which copies all the duplicate variables and then use that array for sum.
Also, if you want to add the particular number as many times as it appears, I will suggest creating a table like the one in sparse matrices
and then referring to it during addition.
This logic, though not space efficient, is very easy to implement.
Here is an approach with a single Array.reduce and nothing else. No Array.indexOf or Array.lastIndexOf. Although it might not be as concise it does not traverse the array looking for indexes multiple times nor does any Array.filter:
const sumDubs = arr => arr.reduce((r,c) => {
if(r[c]) {
r[c] += 1
r.sum += r[c] > 2 ? (r[c]*c) - ((r[c]-1)*c) : r[c]*c
} else r[c] = 1
return r
}, { sum: 0 }).sum
console.log(sumDubs([1, 1, 2, 3, 3])) // 8
console.log(sumDubs([1, 2, 1, 2, 2, 3, 3])) // 14
console.log(sumDubs([1, 2, 1, 2, 2, 3, 3, 3, 1, 2, 4, 4])) // 28
The idea is to keep track of the on-going sum via a property in the accumulator of the Array.reduce and simply keep calculating the sum based on which number is duplicated and more importantly how many times.

Optimize- get third largest num in array

So, I was working on this challenge to return the third largest number in an array. I had got it worked out until I realized that I must account for repeat numbers. I handled this by adding 3 layers of for loops with variables i, j, and k. You'll see what I mean in the code. This is not terribly efficient or scalable.
My question is, how can I optimize this code? What other methods should I be using?
function thirdGreatest (arr) {
arr.sort(function(a, b) {
if (a < b) {
return 1;
} else if (a > b) {
return -1;
} else {
return 0;
}
});
for ( var i = 0; i < arr.length; i++) {
for (var j = 1; j < arr.length; j++) {
for (var k = 2; k < arr.length; k++) {
if (arr[i] > arr[j]) {
if (arr[j] > arr[k]) {
return arr[k];
}
}
}
}
}
}
console.log(thirdGreatest([5, 3, 23, 7,3,2,5,10,24,2,31, 31, 31])); // 23
console.log(thirdGreatest([5, 3, 23, 7,3,2,5,10,24,2,31])) // 23
console.log(thirdGreatest([5, 3, 7, 4])); // 4
console.log(thirdGreatest([2, 3, 7, 4])); // 3
Since you already sorted the array, it seems like you should be fine iterating over the list and keep track of the numbers you have already seen. When you have seen three different numbers, return the current one:
var seen = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== seen[0]) {
if (seen.length === 2) {
return arr[i];
}
seen.unshift(arr[i]);
}
}
function thirdGreatest (arr) {
arr.sort(function(a, b) {
return b - a;
});
var seen = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== seen[0]) {
if (seen.length === 2) {
return arr[i];
}
seen.unshift(arr[i]);
}
}
}
console.log(thirdGreatest([5, 3, 23, 7,3,2,5,10,24,2,31, 31, 31])); // 23
console.log(thirdGreatest([5, 3, 23, 7,3,2,5,10,24,2,31])) // 23
console.log(thirdGreatest([5, 3, 7, 4])); // 4
console.log(thirdGreatest([2, 3, 7, 4])); // 3
Note: You can simplify the sort callback to
arr.sort(function(a, b) {
return b - a;
});
// With arrow functions:
// arr.sort((a, b) => b - a);
The callback has to return a number that is larger, smaller or equal to 0, it doesn't have to be exactly -1 or 1.
A one-"line"r using Set to remove duplicates
Array.from(new Set(arr)).sort(function(a, b) {
return b - a;
})[2];
Set now has reasonable browser support
The optimal solution is to do this in a single pass O(n) time. You do not need to sort the array - doing so makes your solution at-least (n log n).
To do this in as single pass, you simply need three temporary variables: largest, secondLargest, thirdLargest. Just go through the array and update these values as necessary (i.e. when you replace largest it becomes second largest, etc...). Lastly, when you see duplicates (i.e. currentValue == secondLargest), just ignore them. They don't affect the outcome.
Don't forget to check for edge cases. You cannot provide an answer for [2, 2, 2, 2, 2] or [3, 2].
Try to think about what data structure you can use here. I suggest a set. Every time you add a nested loop your function gets exponentially slower.
Edited:
function thirdGreatest(arr) {
var s = Array.from(new Set(arr)).sort(function(a, b) {
return a - b;
})
return s[2] || s[1] || s[0] || null;
}
Working Example
We need to be able to handle:
[1,2,1,2] // 2
[1,1,1,1] // 1
[] // null
This assumes that you get an array passed in.
If you do not have a third largest number, you get the second.
If you do not have a second largest you get the first largest.
If you have no numbers you get null
If you want the 3rd largest or nothing, return s[2] || null
Many of the other answers require looping through the initial array multiple times. The following sorts and deduplicates at the same time. It's a little less terse, but is more performant.
const inputArray = [5,3,23,24,5,7,3,2,5,10,24,2,31,31,31];
const getThirdGreatest = inputArray => {
const sorted = [inputArray[0]]; // Add the first value from the input to sorted, for our for loop can be normalized.
let migrated = false;
let j;
for(let i = 1; i<inputArray.length; i++) { // Start at 1 to skip the first value in the input array
for(j=0; j<sorted.length; j++) {
if(sorted[j] < inputArray[i]) {
// If the input value is greater than that in the sorted array, add that value to the start of the sorted array
sorted.splice(j,0,inputArray[i]);
migrated = true;
break;
} else if(sorted[j] === inputArray[i]) {
// If the input value is a duplicate, ignore it
migrated = true;
break;
}
}
if(migrated === false) {
// If the input value wasn't addressed in the loop, add it to the end of the sorted array.
sorted[sorted.length] = inputArray[i];
} else {
migrated = false;
}
}
// Return the third greatest
return sorted[2];;
};
const start = performance.now();
getThirdGreatest(inputArray); // 23
const end = performance.now();
console.log('speed: ', end - start); // 0.1 - 0.2ms
One single iteration O(n) and very fast method of doing this is making your own Set like object. The advantageous point is making no comparisons at all when constructing our "sorted" list of "unique" elements which brings enormous efficiency. The difference is very noticeable when dealt with huge lists like in the lengths exceeding 1,000,000.
var arr = [5, 3, 23, 7,3,2,5,10,24,2,31, 31, 31],
sorted = Object.keys(arr.reduce((p,c)=> (p[c] = c, p),Object.create(null))),
third = sorted[sorted.length-3];
document.write(third);
If you think Object.keys might not return a sorted array (which i haven't yet seen not) then you can just sort it like it's done in the Set method.
Here i tried it for 1,000,000 item array and returns always with the correct result in around 45msecs. A 10,000,000 item array would take like ~450msec which is 50% less than other O(n) solutions listed under this question.
var arr = [],
sorted = [],
t0 = 0,
t1 = 0,
third = 0;
for (var i = 0; i<1000000; i++) arr[i] = Math.floor(Math.random()*100);
t0 = performance.now();
sorted = Object.keys(arr.reduce((p,c)=> (p[c] = c, p),Object.create(null)));
third = sorted[sorted.length-3];
t1 = performance.now();
document.write(arr.length + " size array done in: " + (t1-t0) + "msecs and the third biggest item is " + third);

Callbacks & manipulating arrays but getting undefined back

Practicing callbacks & higher ordered function & found this question online.
var merge = function(array1, array2, callback){
//your code here.
}
var x = merge([1, 2, 3, 4], [5, 6, 7, 8], function(a, b){
return a + b;
});
//x should now equal [6, 8, 10, 12].
Here's my take on this problem.
var merge = function(array1, array2, callback){
for(var i = 0; i < array1.length; i++) {
callback(array1[i], array2[i]);
}
}
var x = merge([1, 2, 3, 4], [5, 6, 7, 8], function(a, b){
return a + b;
});
When I console.log(x), the console returns "undefined" so I'm guessing it has to do w/ the value of x not being an array. I can see that the math is being done correctly though, for when I change "return a + b" to "console.log(a + b)" I get the right numbers but just not in array form. Can anyone point me towards the right direction?
You are calling the callback, but you are ignoring the value returned by it. You should accumulate all the values in an array and your should return the array from merge.
For example,
function merge(array1, array2, callback) {
// Define an array object to accumulate the results from `callback`
var result = [];
for (var i = 0; i < array1.length; i++) {
// Accumulate the result of `callback` in `result` array
result.push(callback(array1[i], array2[i]));
}
// Return the `result` array
return result;
}
Note: If the arrays are of different sizes then running the loop based on array1's length will not be correct always. So, you might want to either
go with the smallest length of two arrays and ignore elements from the longer array
or use a default value for the elements of the shorter array.
If you choose go with the first method, then you just need to adjust the loop condition, like this
var minLen = Math.min(array1.length, array2.length);
for (var i = 0; i < minLen; i++) {
...
If you choose to go with the second method, then you need to run till the maximum of two arrays and use default values, like this
var maxLen = Math.max(array1.length, array2.length);
for (var i = 0; i < maxLen; i++) {
result.push(callback(array1[i] || 0, array2[i] || 0));
}
Here, if the value of array1[i] returns undefined (if the index is not found in an array, undefined will be returned), it means that array1 is shorter than array2, so the default value 0 will be used.

Categories

Resources