Shuffle a cartesian product iterator in Javascript - javascript

Using a library like https://www.npmjs.com/package/cartesian-product-generator I am able to generate a cartesian product iterator. This is necessary when I have, say 4 arrays, each of length 100, giving 100 million combinations - which is too memory intensive to generate an array of.
However, I want to randomise these 100 million combinations, so that I can iterate over all of them in a random order. How can I randomise the order of iteration without bringing all 100 million items into memory?

You can use the following trick.
Think of a mapping from elements to numbers.
Given Peters example:
// ---------- 2 1 0
const arr1 = ['r', 'g', 'b'];
const arr2 = ['early', 'late'];
const arr3 = ['high', 'low'];
You can map arr1 to (0, 1, 2), arr2 to (0, 1) and arr3 to (0, 1).
So a 3 digit number like 201 would encode r-late-high.
But you can transform the mapping to a decimal number, it's 3*2*2=12 combinations, so your decimal number would require 2 digits or fit into 4 Bits ("1001").
This way, your 12 combinations can be shuffled by generating the numbers 0-11 and by shuffling these. But better: Generate a random number from 0-11 and use an array of bits to mark every combination, which has been seen/tested.
In a trivial case, every array would be of size 10 and you could use the number directly as key for your values. The last digit denotes the element of the last array, the first digit of the first array and so on.
It needs a bit more concentration for working with different array sizes and number below and above 10, but it's simple multiplication/division and modulo operation.
For example, given 3 Arrays of size 212, 7 and 19, you have 212719 = 28196 possible combinations.
You generate sample=rnd (28196) and get 13936. Now you take that value modulo 19 (% arr3.length()) which is 9. So the element at index 9 of the 3rd array is picked.
You take 13936/19 and get 733. 733 % 7 is 5, so this denotes the 5th element of arr2.
733/7=104, 104%212 is 104 so you pick the 104th element from arr1. This is reproducible and fast. In your bit array, you mark the Bit at index 13936 as true (default: false).
I wrote a similar algorithm not in JS, but Scala, and it is super fast.
If you happen to search nearly your whole array, it will get slow when 99.999% of the array is already marked "true", and repeated random numbers always return an already tested value. Then you might be better off visiting the elements in order or counting the seen elements and only generating a random number from size-count, then iterating over the bit array and skipping all seen bits. You could even start with strategy 1 and switch to the second one when things slow down.

You can't use an iterator to randomize the result because you'd need to have all arrays in memory to randomize.
You can however build your own random cartesian product generator:
function randomCartesianProduct() {
return Object.keys(arguments).map(key => {
let arr = arguments[key];
return arr[Math.floor(Math.random() * arr.length)]
});
}
const arr1 = ['r', 'g', 'b'];
const arr2 = ['early', 'late'];
const arr3 = ['high', 'low'];
for(let i = 1; i <= 50; i++) {
let r = randomCartesianProduct(arr1, arr2, arr3);
console.log(i + ': ' + r.join(', '));
}
UPDATE 1 based on additional requirement to avoid repeating any products:
class RandomCartesianProduct {
constructor(...args) {
this.arrays = args;
this.productSize = this.arrays.reduce((acc, arr) => acc * arr.length, 1);
this.seen = {};
this.seenSize = 0;
}
next() {
if(this.seenSize >= this.productSize) {
return null; // all combinations exhausted
}
let result;
let hash;
do {
let idxes = [];
result = this.arrays.map(arr => {
let idx = Math.floor(Math.random() * arr.length);
idxes.push(idx);
return arr[idx];
});
hash = idxes.join('-');
} while(this.seen[hash]);
this.seen[hash] = true;
this.seenSize++;
return result;
}
};
const arr1 = ['r', 'g', 'b'];
const arr2 = ['early', 'late'];
const arr3 = ['high', 'low'];
const random = new RandomCartesianProduct(arr1, arr2, arr3);
for(let i = 1; i <= 16; i++) {
let r = random.next();
console.log(i + ': ' + r);
}
Output: (one of many random ones)
1: g,late,low
2: g,late,high
3: g,early,low
4: r,early,low
5: b,early,low
6: r,early,high
7: b,early,high
8: b,late,low
9: g,early,high
10: b,late,high
11: r,late,low
12: r,late,high
13: null
14: null
15: null
16: null
A this.seen hash tracks already returned random values. The hash is based on the random indexes into arrays, e.g. is relatively short.
Once all combinations are exhausted a null is returned.
Keep in mind that running this with many & big arrays will get slower over time because of many retries, e.g. hits on seen items will increase over time.

Related

How to get sums of subarrays

While solving online code exercises, I came across this one:
Given a 1-dimensional array of numbers and the number of queries, where each query has start index a, end index b and a number c, find the sum of numbers between indexes a and b (inclusive). For each occurrence of zero within the range [a,b], add the value of c to the sum. For example, numbers = [4,6,0,10], queries = [1,3,20] => for this example we need to get the sum of [4,6,0] (indexes 1-3), and because [4,6,0] has 0, we also need to add 20.
This is my code so far:
function findSum(numbers, queries) {
//declare empty array that will store the numbers
let arr = []
// declare initial sum
let sum = 0;
// get the last element of queries (c)
let lastElement = queries[0].pop()
// loop through queries and push numbers to arr, to sum them in the end
queries[0].slice(0, 2).forEach(x => {
arr.push(numbers[x - 1])
})
// check if arr has 0
let zero = arr.filter(el => el === 0)
// if arr has 0, according to the instructions we need to add the c of the q
if (zero.length != 0) {
sum = arr.reduce((a, b) => a + b, 0) + lastElement
}
else {
sum = arr.reduce((a, b) => a + b, 0)
}
return sum
}
My code works if queries is an array, but in some test cases queries may be array of arrays like [ [ 2, 2, 20 ], [ 1, 2, 10 ] ]. I don't know know how to check the numbers in case if queries is array of arrays. Any suggestions are greatly appreciated.
in some test cases queries may be array of arrays
I would expect that this would always be the case, not just in some cases. This is also clear from your code:
queries[0].pop()
This assumes a 2-dimensional array! The problem is not that you sometimes get a 1-dimensional array and other times a 2-dimensional array. The problem is that although you always get a 2-dimensional array, your code is only looking at the first query -- the one that sits at queries[0].
Instead, you should loop over all queries.
I also assume that the return value of your function must be an array, having an answer for each of the queries. This means that you probably want to have code like this:
function findSum(numbers, queries) {
return queries.map(query => {
// solve the single query
return sum;
});
}
Note that your code is not making the sum correctly, as your arr will have a length of 2 (arr.push(numbers[x - 1]) is executed exactly twice), yet the query could indicate a range with 100 values and you should derive the sum of those 100 values, not just of two.
But even if you fix all that, you'll end up with an inefficient solution that will have to iterate over many values in the input array multiple times. This needs a smarter approach.
Try to think of a way to analyse the input before processing any queries yet. Would there be something useful you could build that would help to quickly get a sum of a subarray without having to iterate that subsection again?
Here are some hints:
Hint #1
Use the following truth:
sum(numbers.slice(start, end)) == sum(numbers.slice(0, end)) - sum(numbers.slice(0, start - 1))
Hint #2
What if you would know the sum from the start of the array to any given index? Like a running sum... So for numbers=[4, 8, 0, 3] you would know [4, 12, 12, 15]. Would that help in calculating a sum for a certain range of [start, end]?
Hint #3
How could you apply the same principle for the special treatment of zeroes?

Find the minimal sum of the local maximum of the subarrays of an array

I need to write a function to calculate minimal sum of the local maximum of the subarrays of an array where every item is a positive integer with possible duplicates.
For example, we have an array [2, 3, 1, 4, 5, 6] and the number of sub arrays is 3. We want to get the min sum of the local max of the sub arrays. What that means is that, for example, one possible way to divide the current array into 3 sub arrays is [[2,3], [1], [4,5,6]] and the local maxs for each subarray is 3, 1, 6 respectively. And the sum of the local maxs is 3 + 1 + 6 = 10. For this particular array, [2, 3, 1, 4, 5, 6], this is the minimal sum of all its possible sub-array variations.
My approach is to first get all the possible sub-array variations for a given array. and get the min sum of them.
function getSubarrays(array, numOfSubarray) {
const results = []
const recurse = (index, subArrays) => {
if (index === array.length && subArrays.length === numOfSubarray) {
results.push([...subArrays])
return
}
if (index === array.length) return
// 1. push current item to the current subarray
// when the remaining items are more than the remaining sub arrays needed
if (array.length - index - 1 >= numOfSubarray - subArrays.length) {
recurse(
index + 1,
subArrays.slice(0, -1).concat([subArrays.at(-1).concat(array[index])])
)
}
// 2. start a new subarray when the current subarray is not empty
if (subArrays.at(-1).length !== 0)
recurse(index + 1, subArrays.concat([[array[index]]]))
}
recurse(0, [[]], 0)
return results
}
function getMinSum(arrays) {
return arrays.reduce(
(minSum, array) =>
Math.min(
minSum,
array.reduce((sum, subarray) => sum + Math.max(...subarray), 0)
),
Infinity
)
}
getMinSum(getSubarrays([[2,3], [1], [4,5,6]], 3)) // 10
However, I think the time complexity for my solution is really high. My guess is that it is on the order of 2^n (feel free to correct me if I am wrong). I wonder if there is a more efficient way to calculate this.
The first thing that comes to mind is dynamic programming.
Let dp[i][j] be the minimal sum of local maximums of array[0..i] (left border included, right border excluded) divided into j subarrays. dp[0][0] = 0 (this is an initial value for empty array[0..0]), and for simplicity initialise all other dp[i][j] with some large enough number to denote meaningless of not calculated values (larger than sum of elements in array in this case is enough).
Your answer obviously is the value of dp[array.length][numOfSubarray].
How do you calculate values of the dp? Actually pretty easy. dp[i][j] is the minimum among dp[k][j - 1] + max(array[k..i]) (where k < i). Let's analyse this formula:
dp[k][j - 1] + max(array[k..i])
# ^ ^
# This term is the minimal sum of local maximums
# of array[0..k] divided into j-1 subarrays.
# |
# This term is maximum of your new j-th subarray.
Also make sure that all the dp[k][j - 1] were calculated beforehand (for example by calculating dp[i][1] at first, then dp[i][2], then dp[i][3] and so on).
Now let's write it altogether (naive approach just for now).
dp[0][0] = 0
for newSubarrayNumber in range(1, numOfSubarray + 1):
for previousEnd in range(0, array.length):
for newEnd in range(previousEnd + 1, array.length + 1):
# Formula from above.
dp[newEnd][newSubarrayNumber] =
min(dp[newEnd][newSubarrayNumber],
dp[previousEnd][newSubarrayNumber - 1] + max(array[previousEnd..newEnd]))
# Your answer.
print(dp[array.length][numOfSubarray])
As you can see we've got polynomial complexity, now it's O(numOfSubarray * array.length^3) (two array.length for two nested loops and one more because of max(array[previousEnd..newEnd])).
Also we can optimise our algorithm. It makes no sense to always calculate min(array[previousEnd..newEnd]), because previously we did that for newEnd - 1 and we can reuse that value. That brings us to the following algorithm:
for newSubarrayNumber in range(1, numOfSubarray + 1):
for previousEnd in range(0, array.length):
maxElement = 0
for newEnd in range(previousEnd + 1, array.length + 1):
# maxElement replaces max(array[previousEnd..newEnd]).
maxElement = max(array[newEnd - 1], maxElement)
dp[newEnd][newSubarrayNumber] =
min(dp[newEnd][newSubarrayNumber],
dp[previousEnd][newSubarrayNumber - 1] + maxElement)
That's O(numOfSubarray * array.length^2) (just because of loops, no extra complexity multiplier).
I believe one can optimise it even more (maybe using some advanced data structures), feel free to comment. Also even better approach for this particular problem could be some sort of greedy algorithm (like having small subarrays closer to border of an array and one big chunk in the center but that needs to be proven).
We can have O(n^2) by using a dynamic program where the state is (1) the index of the rightmost element considered so far, and (2) the length of the subarray ending at (1).
The information we need to store about those two things is deterministic -- we need the maximum value in subarray (2) and the best solution overall ending at (1).
Then, to consider the next element, we have two choices for each length in (2): either add the element to the subarray, or start a new subarray.
For each element, we would examine O(n) lengths for a total of O(n^2).

Why does an object exist in two sum solution? [duplicate]

Im just wondering who can explain the algorithm of this solution step by step. I dont know how hashmap works. Can you also give a basic examples using a hashmap for me to understand this algorithm. Thank you!
var twoSum = function(nums, target) {
let hash = {};
for(let i = 0; i < nums.length; i++) {
const n = nums[i];
if(hash[target - n] !== undefined) {
return [hash[target - n], i];
}
hash[n] = i;
}
return [];
}
Your code takes an array of numbers and a target number/sum. It then returns the indexes in the array for two numbers which add up to the target number/sum.
Consider an array of numbers such as [1, 2, 3] and a target of 5. Your task is to find the two numbers in this array which add to 5. One way you can approach this problem is by looping over each number in your array and asking yourself "Is there a number (which I have already seen in my array) which I can add to the current number to get my target sum?".
Well, if we loop over the example array of [1, 2, 3] we first start at index 0 with the number 1. Currently, there are no numbers which we have already seen that we can add with 1 to get our target of 5 as we haven't looped over any numbers yet.
So, so far, we have met the number 1, which was at index 0. This is stored in the hashmap (ie object) as {'1': 0}. Where the key is the number and the value (0) is the index it was seen at. The purpose of the object is to store the numbers we have seen and the indexes they appear at.
Next, the loop continues to index 1, with the current number being 2. We can now ask ourselves the question: Is there a number which I have already seen in my array that I can add to my current number of 2 to get the target sum of 5. The amount needed to add to the current number to get to the target can be obtained by doing target-currentNumber. In this case, we are currently on 2, so we need to add 3 to get to our target sum of 5. Using the hashmap/object, we can check if we have already seen the number 3. To do this, we can try and access the object 3 key by doing obj[target-currentNumber]. Currently, our object only has the key of '1', so when we try and access the 3 key you'll get undefined. This means we haven't seen the number 3 yet, so, as of now, there isn't anything we can add to 2 to get our target sum.
So now our object/hashmap looks like {'1': 0, '2': 1}, as we have seen the number 1 which was at index 0, and we have seen the number 2 which was at index 1.
Finally, we reach the last number in your array which is at index 2. Index 2 of the array holds the number 3. Now again, we ask ourselves the question: Is there a number we have already seen which we can add to 3 (our current number) to get the target sum?. The number we need to add to 3 to get our target number of 5 is 2 (obtained by doing target-currentNumber). We can now check our object to see if we have already seen a number 2 in the array. To do so we can use obj[target-currentNumber] to get the value stored at the key 2, which stores the index of 1. This means that the number 2 does exist in the array, and so we can add it to 3 to reach our target. Since the value was in the object, we can now return our findings. That being the index of where the seen number occurred, and the index of the current number.
In general, the object is used to keep track of all the previously seen numbers in your array and keep a value of the index at which the number was seen at.
Here is an example of running your code. It returns [1, 2], as the numbers at indexes 1 and 2 can be added together to give the target sum of 5:
const twoSum = function(nums, target) {
const hash = {}; // Stores seen numbers: {seenNumber: indexItOccurred}
for (let i = 0; i < nums.length; i++) { // loop through all numbers
const n = nums[i]; // grab the current number `n`.
if (hash[target - n] !== undefined) { // check if the number we need to add to `n` to reach our target has been seen:
return [hash[target - n], i]; // grab the index of the seen number, and the index of the current number
}
hash[n] = i; // update our hash to include the. number we just saw along with its index.
}
return []; // If no numbers add up to equal the `target`, we can return an empty array
}
console.log(twoSum([1, 2, 3], 5)); // [1, 2]
A solution like this might seem over-engineered. You might be wondering why you can't just look at one number in the array, and then look at all the other numbers and see if you come across a number that adds up to equal the target. A solution like that would work perfectly fine, however, it's not very efficient. If you had N numbers in your array, in the worst case (where no two numbers add up to equal your target) you would need to loop through all of these N numbers - that means you would do N iterations. However, for each iteration where you look at a singular number, you would then need to look at each other number using a inner loop. This would mean that for each iteration of your outer loop you would do N iterations of your inner loop. This would result in you doing N*N or N2 work (O(N2) work). Unlike this approach, the solution described in the first half of this answer only needs to do N iterations over the entire array. Using the object, we can find whether or not a number is in the object in constant (O(1)) time, which means that the total work for the above algorithm is only O(N).
For further information about how objects work, you can read about bracket notation and other property accessor methods here.
You may want to check out this method, it worked so well for me and I have written a lot of comments on it to help even a beginner understand better.
let nums = [2, 7, 11, 15];
let target = 9;
function twoSums(arr, t){
let num1;
//create the variable for the first number
let num2;
//create the variable for the second number
let index1;
//create the variable for the index of the first number
let index2;
//create the variable for the index of the second number
for(let i = 0; i < arr.length; i++){
//make a for loop to loop through the array elements
num1 = arr[i];
//assign the array iteration, i, value to the num1 variable
//eg: num1 = arr[0] which is 2
num2 = t - num1;
//get the difference between the target and the number in num1.
//eg: t(9) - num1(2) = 7;
if(arr.includes(num2)){
//check to see if the num2 number, 7, is contained in the array;
index1 = arr.indexOf(num2);
//if yes get the index of the num2 value, 7, from the array,
// eg: the index of 7 in the array is 1;
index2 = arr.indexOf(num1)
//get the index of the num1 value, which is 2, theindex of 2 in the array is 0;
}
}
return(`[${index1}, ${index2}]`);
//return the indexes in block parenthesis. You may choose to create an array and push the values into it, but consider space complexities.
}
console.log(twoSums(nums, target));
//call the function. Remeber we already declared the values at the top already.
//In my opinion, this method is best, it considers both time complexity and space complexityat its lowest value.
//Time complexity: 0(n)
function twoSum(numbers, target) {
for (let i = 0; i < numbers.length; i++) {
for (let j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] === target) {
return [numbers.indexOf(numbers[i]), numbers.lastIndexOf(numbers[j])];
}
}
}
}

JS: Finding unpaired elements in an array

I have the following question (this is not school -- just code site practice questions) and I can't see what my solution is missing.
A non-empty array A consisting of N integers is given. The array contains an odd number of elements, and each element of the array can be paired with another element that has the same value, except for one element that is left unpaired.
Assume that:
*N is an odd integer within the range [1..1,000,000];
*each element of array A is an integer within the range [1..1,000,000,000];
*all but one of the values in A occur an even number of times.
EX: A = [9,3,9,3,9,7,9]
Result: 7
The official solution is using the bitwise XOR operator :
function solution(A) {
var agg = 0;
for(var i=0; i<A.length; i++) {
agg ^= A[i];
}
return agg;
}
My first instinct was to keep track of the occurrences of each value in a Map lookup table and returning the key whose only value appeared once.
function solution(A) {
if (A.length < 1) {return 0}
let map = new Map();
let res = A[0]
for (var x = 0; x < A.length; x++) {
if (map.has(A[x])) {
map.set(A[x], map.get(A[x]) + 1)
} else {
map.set(A[x], 1)
}
}
for ([key,value] of map.entries()) {
if (value===1) {
res = key
}
}
return res;
}
I feel like I handled any edge cases but I'm still failing some tests and it's coming back with a 66% correct by the automated scorer.
You could use a Set and check if deletion deletes an item or not. If not, then add the value to the set.
function check(array) {
var s = new Set;
array.forEach(v => s.delete(v) || s.add(v));
return s.values().next().value;
}
console.log(check([9, 3, 9, 7, 3, 9, 9])); // 7
You're not accounting for cases like this:
[ 1, 1, 2, 2, 2 ] => the last 2 is left unpaired
So your condition should be if ( value % 2 ) instead of if ( value === 1 ).
I think also there is not much benefit to using a Map rather than just a plain object.
The official solution works due to the properties of the bitwise XOR (^), namely the fact that a ^ a == 0, a ^ 0 == a, and that the operation is commutative and associative. This means that any two equal elements in the array will cancel each other out to become zero, so all numbers appearing an even amount of times will be removed and only the number with an odd frequency will remain. The solution can be simplified using Array#reduce.
function findOdd(arr) {
return arr.reduce((a,c)=>a ^ c, 0);
}
You need not to make a count of each and traverse again, if you are sure that there will be exactly one number which will occur odd number of times. you can sum the array and do + when odd entry and - when even entry (to dismiss it back) and in the hash (map or object) you can just toggle for subsequent entry of each number.
Here is an example:
let inputArray1 = [10,20,30,10,50,20,20,70,20,70,50, 30,50], //50 -> 3 times
inputArray2 = [10,20,30,20,10], //30 -> 1 time
inputArray3 = [7,7,7,7,3,2,7,2,3,5,7]; //5 -> 1 time
let getOddOccuence = arr => {
let hash = {};
return arr.reduce((sum, n) => sum + ((hash[n] = !hash[n]) ? n : -n), 0);
}
console.log('Input Array 1: ', getOddOccuence(inputArray1));
console.log('Input Array 2: ', getOddOccuence(inputArray2));
console.log('Input Array 3: ', getOddOccuence(inputArray3));
In case the input contains multiple or no numbers having odd number of occurance (if you are not sure there) then you have already the hash (and you can ignore performing sum) and return the keys of hash (where value is true (and not checking with %2 and then consider as truthy or false in case of you have count))
function solution(A) {
let result = 0;
for (let element of A) {
// Apply Bitwise XOR to the current and next element
result ^= element;
}
return result;
}
const unpaired = solution([9, 3, 9, 3, 9, 7, 9]);
console.log(unpaired);
Source: https://gist.github.com/k0ff33/3eb60cfb976dee0a0a969fc9f84ae145

Filtering an array and converting it to a 2D array

I saw this interesting post yesterday, and thought it'd be important to know how to create a 2D array from sorting the given argument:
How to get even numbers array to print first instead of odds?
Below is the code snippet from Ori Drori.
I was curious to know what line of code, and which expression sorts the data and creates 2D array. I assume it's something to do with [numbersArray[i] % 2], but isn't the remainder operator returns the remainder left over?
Also it's a bit confusing as it just set one bracket for an array and use push() to make 2 different arrays.
Any reference that'd help me to understand this will also be much appreciated- thanks!
var numbersArray = [1,2,34,54,55,34,32,11,19,17,54,66,13];
function divider(numbersArray) {
var evensOdds = [[], []];
for (var i = 0; i < numbersArray.length; i++) {
evensOdds[numbersArray[i] % 2].push(numbersArray[i]);
}
return evensOdds;
}
console.log(divider(numbersArray));
evensOdds has 2 array elements. evensOdds[0] represents first array, which will hold even nums. evensOdds[1] is second element and will hold odd numbers.
When you % 2 an even number, it will result in 0 and 1 for odd number. So when iterating through the array, you % 2, which will return 0 or 1 which enables you to access the first or second array in your evensOdds array and insert it.
The resulting arrays are not sorted but does represent the split between even and odd numbers. To have a sorted results, you will need the following:
var numbersArray = [1,2,34,54,55,34,32,11,19,17,54,66,13];
function divider(numbersArray) {
var evensOdds = [[], []];
for (var i = 0; i < numbersArray.length; i++) {
evensOdds[numbersArray[i] % 2].push(numbersArray[i]);
}
return evensOdds.map(array=> array.sort((a,b)=>a-b));
}
console.log(divider(numbersArray));
In the shared code the evensOdds[numbersArray[i] % 2] line is the part that filter the numbers and inserts them in the respective array, using the indexes 0 and 1 returned from the numbersArray[i] % 2 expression:
If it returns 0 so it's an even number and it will be pushed in the first array otherwise if it returns 1 it's an odd number and will be pushed in the second array.
Another alternative:
Well you can simply use Array.filter() method to filter both evens and odds arrays:
Demo:
var numbersArray = [1, 2, 34, 54, 55, 34, 32, 11, 19, 17, 54, 66, 13];
var evens = numbersArray.filter(function(el) {
return el % 2 == 0;
});
var odds = numbersArray.filter(function(el) {
return el % 2 == 1;
});
console.log(evens);
console.log(odds);

Categories

Resources