jQuery: Cycle through elements to create combo variations - javascript

Premise: An eCommerce system with variations.
Example HTML markup for use in this question.
<div class = "variationType" data-variation-type = "size">
<h3>Colour</h3>
<div class = "variation" data-variation-type = "size"> small </div>
<div class = "variation" data-variation-type = "size"> large </div>
</div>
<div class = "variationType" data-variation-type = "colour">
<h3>Colour</h3>
<div class = "variation" data-variation-type = "colour"> red </div>
<div class = "variation" data-variation-type = "colour"> blue </div>
</div>
<div class = "variationType" data-variation-type = "material">
<h3>Material</h3>
<div class = "variation" data-variation-type = "material"> stone </div>
<div class = "variation" data-variation-type = "material"> wood </div>
</div>
What I need to do is cycle through each of the above variation types and variation names to create a list of all possible variation combos (at least one variation from each variation type). This is stupid easy if there are only two variation types, but I'd need this to work with 3+ as well.
My thinking is that I need to somehow implement a pathing algorithm to go through each variation type and create the unique list, but I don't know how to do this.
In order to get the total amount of possible variations, I am doing the following.
// figure out how many variations there needs to be
var totalPathsToTake = 0;
var totalPathsTaken = 0;
jQuery('.variationType').each(function(i) {
var variationType = jQuery(this).attr("data-variation-type");
var totalVariationsInType = jQuery('.variation[data-variation-type="' + variationType + '"]').length;
if(totalPathsToTake == 0){
totalPathsToTake = totalPathsToTake + totalVariationsInType;
} else {
totalPathsToTake = totalPathsToTake * totalVariationsInType;
}
});
console.log("total variations " + totalPathsToTake)
So the above code will respond with 8, which is correct.
The problem is, now what? How would I go about creating these variations? Any help or advice is insanly appreciated!

From what I understand, what you want is all the permutations of div.variations.
One way to do it is to realize that as we permute one variation type we are always reusing the permutations of the variation types that follow it. This lends itself to a recursive implementation like this:
// make an array of variation types, and each entry is the list of possible values
const all = $('.variationType').map((i, el) => $('.variation', el)).toArray();
// recursive function, return all permutations of values for a array of variation types
function permute_variations(arr) {
// base case of no variation types
if (arr.length < 1)
return [];
// base case of a single variation type
if (arr.length === 1)
return arr[0].toArray();
// recurse by getting the permutations of the following variation types
const inner_arr = permute_variations(arr.slice(1));
// now permute all values for this type with the permutations that we got
return arr[0].map((i, v0) => inner_arr.map(e => [v0].concat(e))).toArray();
}
const result = permute_variations(all);
console.log(`Number of permutations: ${result.length}`);
console.log(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="variationType" data-variation-type="size">
<h3>Colour</h3>
<div class="variation" data-variation-type="size"> small </div>
<div class="variation" data-variation-type="size"> large </div>
</div>
<div class="variationType" data-variation-type="colour">
<h3>Colour</h3>
<div class="variation" data-variation-type="colour"> red </div>
<div class="variation" data-variation-type="colour"> blue </div>
</div>
<div class="variationType" data-variation-type="material">
<h3>Material</h3>
<div class="variation" data-variation-type="material"> stone </div>
<div class="variation" data-variation-type="material"> wood </div>
</div>
The result is an array of triples of div.variation elements and you can process it as you like.
Note: be careful of the difference between the Array map() method and the jQuery map() method, as you can see they call the lambda function with the index and element parameters in opposite order.

I'm not a native English speaker
so I can't explain it well
but I'll try my best
that would be appreciated if there's any suggestion
First, I simplify the problem to find combinations of two array
var arr1 = ["0","1"]
var arr2 = ["0","1"]
and we all know that the results would be like:
(first char comes from arr1)
(last char comes from arr2)
00
01
10
11
Then I change the values of the second array to
var Second = ["0","1","2"]
the results are:
00
01
02
10
11
12
after this, I add another array
var Third = ["0","1"]
the results are:
000
001
010
011
020
021
100
101
110
111
120
121
I found there's a logic to determine how many times would a single num repeat and whole array values repeat
We can calculate the combinations simply by multiple each array's length
(if array's length is greater than 0)
a single num would repeat combinations / previous array's length product times
and whole array values would repeat combinations / all previous array's length product(including self)
when combinations is 4 in case 1, last char repeat 1 time since there's no previous array
first char repeat 4 / First.length = 2 times
when combinations is 12 in case 3, the last array of result repeat 12/First.length = 6 times
first array repeat 12/(First.length * Second.length * Third.length) = 1 times
so here it is, hope this helps
var Result = []
var Final = []
var Combinations = 1
var First = ["0", "1", "2"]
var Empty = []
var Second = ["a", "b", "c", "d"]
var Third = ["+", "-"]
// assume there would be some empty array
var allList = [First, Second, Empty, Third].filter(n => n.length>0).reverse() // remove empty array and reverse
allList.forEach(arr => Combinations = arr.length > 0 ? Combinations * arr.length : Combinations) // calculate combinations
$("body").append("Combinations : " + Combinations + "<br>")
var Items = 1
var totalRepeatTimes = 1
var elementRepeatTimes = 1
allList.forEach((arr ,index) => {
Items *= arr.length;
// array repeat times
totalRepeatTimes = Combinations/Items;
// each char repeat times
elementRepeatTimes = index > 0 ? elementRepeatTimes * allList[index - 1].length : elementRepeatTimes;
// add result array to Final
Final.push(RepeatArray(arr, elementRepeatTimes, totalRepeatTimes))
})
Result = Array(Combinations).fill("."); // prepare an array fill with empty string
Final.reverse().forEach(arr => { Result = Join(Result, arr) }) // Join all result array
Result.forEach((result, index)=> $("body").append((index+1) + " : " + result + "<br>") )
function RepeatArray(arr, elementRepeatTimes, totalRepeatTimes) {
arr = RepeatElement(arr, elementRepeatTimes);
arr = RepeatTotal(arr, totalRepeatTimes);
return arr;
}
function RepeatElement(arr, elementRepeatTimes) {
var newArr = [];
arr.forEach(element => {
for (var i = 0; i < elementRepeatTimes; i++) {
newArr.push(element)
}
})
return newArr;
}
function RepeatTotal(arr, totalRepeatTimes) {
var newArr = [];
for (var i = 0; i < totalRepeatTimes; i++) {
newArr = newArr.concat(arr)
}
return newArr;
}
function Join(arr1, arr2) {
var newArr = [];
arr1.forEach((element, index) => {
newArr.push(arr1[index].toString() + arr2[index].toString())
})
return newArr;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Related

How to randomize without replacement with conditions?

I have a number of embedded images, which I randomize 4 times without replacement (once an image is seen, you cannot see it again). I'd like to add a condition, which suggests that a set of additional images cannot be seen (not only the image that was previously selected). These are images that have similar traits to the one selected.
To demonstrate:
Let's say I have the following array of vars:
BF1, BA1, BF2, BA2, BF3, BA3
I want to randomly draw 3 vars (images) out of the array without replacement, AND I want vars that have the number 2 (same set) to be removed from the next array as well. So, if the first drawn var is BF2, the next draw will be from the following array:
BF1, BA1, BF3, BA3 (only one of these options can randomly appear)
Now let's say I drew the var BF1, so the next set of possible vars will be:
BF3, BA3.
I hope this makes sense. This is the code I have so far for the drawing without replacement:
function shuffle(array){
var counter = array.length,
temp, index;
while (counter > 0){
index = Math.floor(Math.random() * counter);
counter = counter-1;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
var myArray = [BF1,BA1,BF2, BA2, BF3,BA3, BA4, BF4, BA5, BF5, BF6, BA6, BF7, BA7, BA8, BF8, BA9, BF9, BF10, BA10, BA11, BF11, BA12, BF12, BA13, BF13, BA14, BF14, BA15, BF15, BA16, BF16, BA17, BF17, BA18, BF18, BA19, BF19, BA20, BF20, BA21, BF21, BF22, BA23, BF23, BA24, BF24, BA25, BF25, BA26, BF26, BA27, BF27, BA28, BF28, BA29, BF29, BA30, BF30, BA31, BF31, BA32, BF33, BA33, BA34, BF35, BA35, BA36, BF36];
shuffle(myArray)
You can definitely implement this in any number of ways, but no matter what you use, you'll need to perform the following 3 steps in some capacity (I split them out into separate methods, but you can combine them as you see fit):
Shuffle the list
Pick an item
Filter out the items matching the pick (in this case, those with the same number)
You have the shuffle routine covered, so that just leaves the pick and the filter.
For the pick, I just used Math.random to pull a random member of the list:
return array[Math.floor(Math.random() * array.length)];
For the filter, I used Array.prototype.filter to pull out the desired items. In this case, with the strings, I parse the number out of the string and then remove any items in the array that have the same number as the last pick:
return array.filter(el => +el.match(/\d+/).join() != +picked.match(/\d+/).join());
But with actual images, you'll just replace that with however you read the labels of your images.
Example
Here's the full working example, with the list of picks first, followed by a sorted array of the picks showing they were all used.
var imageList = ['BF1', 'BA1', 'BF2', 'BA2', 'BF3', 'BA3', 'BA4', 'BF4', 'BA5', 'BF5', 'BF6', 'BA6', 'BF7', 'BA7', 'BA8', 'BF8', 'BA9', 'BF9', 'BF10', 'BA10', 'BA11', 'BF11', 'BA12', 'BF12', 'BA13', 'BF13', 'BA14', 'BF14', 'BA15', 'BF15', 'BA16', 'BF16', 'BA17', 'BF17', 'BA18', 'BF18', 'BA19', 'BF19', 'BA20', 'BF20', 'BA21', 'BF21', 'BF22', 'BA23', 'BF23', 'BA24', 'BF24', 'BA25', 'BF25', 'BA26', 'BF26', 'BA27', 'BF27', 'BA28', 'BF28', 'BA29', 'BF29', 'BA30', 'BF30', 'BA31', 'BF31', 'BA32', 'BF33', 'BA33', 'BA34', 'BF35', 'BA35', 'BA36', 'BF36'];
var selection = imageList.slice();
var picked = [];
function shuffle(array) {
var counter = array.length, temp, index;
while (counter > 0) {
index = Math.floor(Math.random() * counter);
counter = counter - 1;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
}
function pick(array) {
return array[Math.floor(Math.random() * array.length)];
}
function filterPicked(picked, array) {
return array.filter(el => +el.match(/\d+/).join() != +picked.match(/\d+/).join());
}
while (selection.length) {
// 1. Shuffle
shuffle(selection);
// 2. Pick
picked.push(pick(selection));
// 3. Filter
selection = filterPicked(picked[picked.length-1], selection);
}
console.log(`Picks: [${picked.join(', ')}]`);
console.log(`Sorted picks: [${picked.sort((a, b) => +a.match(/\d+/).join() - +b.match(/\d+/).join()).join(', ')}]`);
Step-by-step
Shuffle the selection array (a copy of the full array or all selections)
Pick an item off the selection array, push it onto the array of picks
Filter the selection array to remove items matching the last pick
Repeat 1-3 with each newly filtered array, until no selections remain
You can shuffle array with loop and random numbers, then in another loop extract first image in resulting array, filter array with numbers at the end of string
var myArray="BF1, BA1, BF2, BA2, BF3, BA3, BA4, BF4, BA5, BF5, BF6, BA6, BF7, BA7, BA8, BF8, BA9, BF9, BF10, BA10, BA11, BF11, BA12, BF12, BA13, BF13, BA14, BF14, BA15, BF15, BA16, BF16, BA17, BF17, BA18, BF18, BA19, BF19, BA20, BF20, BA21, BF21, BF22, BA23, BF23, BA24, BF24, BA25, BF25, BA26, BF26, BA27, BF27, BA28, BF28, BA29, BF29, BA30, BF30, BA31, BF31, BA32, BF33, BA33, BA34, BF35, BA35, BA36, BF36";
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
const arr = shuffle(myArray.split(','));
function draw(a, times) {
let res =[]
for (let i = 1; i <= times; i++) {
let str = a[0]
res.push(str)
a = a.filter(a => parseInt(a.match(/\d+$/)[0], 10) !== parseInt(str.match(/\d+$/)[0], 10))
}
return res
}
console.log(draw(arr, 4))

Compare arrays in loop - javascript

I'm doing a lottery system and I need to make sure that each Array is ​​different. This is my code:
var intNumberOfBets = 10;
let aLotteryTicket=[];
let aData = [];
for(intI = 0; intI <intNumberOfBets; intI++){
let oCasilla ={};
oCasilla.block=[];
for(intI = 0; intI <intNumberOfBets; intI++){
let oCasilla ={};
oCasilla.block=[];
Each "lottery ticket" has an array with 5 numbers. They can have the same numbers as others but in different positions.
for (let intB=1;intB<=5;intB++)
{
for(let intA=1;intA<=50; intA++){ aLotteryTicket.push(intA); }
oCasilla.block.push(aLotteryTicket.splice(parseInt(Math.random()*aLotteryTicket.length),1)); // ADD 5 NUMBERS RANDOMLY TO ARRAY
};
oCasilla.block.sort(function (a,b){ return (parseInt(a)-parseInt(b));});
aData.push(oCasilla);
alert(aData[intI].block); // show generated arrays
}//END FOR
How can I prevent each array from being the same as another, before adding it to my final Array aData[]?
Example:If i add the array 5,6,7,8,9 to oCasilla.block=[]; , i need to check that there is not another 5,6,7,8,9 in oCasilla.block=[];
Thanks in advance
You can use a set of string representations (numbers separated by comma built using join(',')) of your tickets to keep track of what was added, and only add if a ticket was not previously created.
function generateTicket() {
// generate an array with 5 unique random numbers
let a = new Set();
while (a.size !== 5) {
a.add(1 + Math.floor(Math.random() * 50));
}
return Array.from(a);
}
let oCasilla = {
block: []
};
let addedTickets = new Set(); // add stingified ticket arrays here
// add 10 unique tickets to oCasilla.block
while (oCasilla.block.length !== 10) {
const ticket = generateTicket();
if (!addedTickets.has(ticket.join(','))) {
oCasilla.block.push(ticket);
addedTickets.add(ticket.join(','));
}
}
console.log(oCasilla);

Get the intersection of n arrays

Using ES6's Set, given two arrays we can get the intersection like so:
let a = new Set([1,2,3])
let b = new Set([1,2,4])
let intersect = new Set([...a].filter(i => b.has(i)));
How can we get the intersection of n arrays?
Update:
I'm trying to wrap my head around this for the following use case. I have a two dimensional array with at least one element.
parts.forEach(part => {
intersection = new Set()
})
How would you get the intersection of each element (array) in parts?
Assuming you have some function function intersect(set1, set2) {...} that can intersect two sets, you can get the intersection of an array of sets using reduce:
function intersect(a, b) {
return new Set(a.filter(i => b.has(i)));
}
var sets = [new Set([1,2,3]), ...];
var intersection = sets.reduce(intersect);
You can create an intersect helper function using a combination of Array methods like .filter(), .map(), and .every().
This answer is inspired by the comment above from Xufox, who mentioned using Array#every in a filter predicate.
function intersect (first = [], ...rest) {
rest = rest.map(array => new Set(array))
return first.filter(e => rest.every(set => set.has(e)))
}
let parts = [
[1, 2, 3],
[1, 2, 4],
[1, 5, 2]
]
console.log(
intersect(...parts)
)
ES6 still has a while
This is the type of function that can easily cause long lags due to excessive amounts of processing. This is more true with the unquestioning and even preferential use of ES6 and array methods like reduce, filter etc, over simple old fashioned loops like while and for.
When calculating the intersection of many sets the amount of work done per iteration should go down if an item has been found not to be part of the intersection. Because forEach can not break you are forced to still iterate all elements. Adding some code to avoid doing the search if the current item has been found to not belong can improve the performance, but it is a real kludge.
The is also the tendency to just create whole new datasets just to remove a single item from an array, set, or map. This is a very bad habit that i see more and more of as people adopt the ES5 way.
Get the intersection of n sets.
So to the problem at hand. Find the intersection of many sets.
Solution B
A typical ES6 solution
function intersectB(firstSet, ...sets) {
// function to intercept two sets
var intersect = (a,b) => {
return new Set([...a].filter(item => b.has(item)))
};
// iterate all sets comparing the first set to each.
sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
// return the result.
return firstSet;
}
var sets = [new Set([1,2,3,4]), new Set([1,2,4,6,8]), new Set([1,3,4,6,8])];
var inter = intersectB(...sets);
console.log([...inter]);
Works well and for the simple test case execution time is under a millisecond. But in my book it is a memory hogging knot of inefficiency, creating arrays, and sets at every line almost and iterating whole sets when the outcome is already known.
Let's give it some more work. 100 sets, with up to 10000 items over 10 tests each with differing amount of matching items. Most of the intercepts will return empty sets.
Warning will cause page to hang up to one whole second... :(
// Create a set of numbers from 0 and < count
// With a odds for any number occurring to be odds
// return as a new set;
function createLargeSet(count,odds){
var numbers = new Set();
while(count-- > 0){
if(Math.random() < odds){
numbers.add(count);
}
}
return numbers;
}
// create a array of large sets
function bigArrayOfSets(setCount,setMaxSize,odds){
var bigSets = [];
for(var i = 0; i < setCount; i ++){
bigSets.push(createLargeSet(setMaxSize,odds));
}
return bigSets;
}
function intersectB(firstSet, ...sets) {
var intersect = (a,b) => {
return new Set([...a].filter(item => b.has(item)))
};
sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
return firstSet;
}
var testSets = [];
for(var i = 0.1; i <= 1; i += 0.1){
testSets.push(bigArrayOfSets(100,10000,i));
}
var now = performance.now();
testSets.forEach(testDat => intersectB(...testDat));
var time = performance.now() - now;
console.log("Execution time : " + time);
Solution A
A better way, not as fancy but much more efficient.
function intersectA(firstSet,...sets) {
var count = sets.length;
var result = new Set(firstSet); // Only create one copy of the set
firstSet.forEach(item => {
var i = count;
var allHave = true;
while(i--){
allHave = sets[i].has(item)
if(!allHave) { break } // loop only until item fails test
}
if(!allHave){
result.delete(item); // remove item from set rather than
// create a whole new set
}
})
return result;
}
Compare
So now let's compare both, if you are feeling lucky try and guess the performance difference, it's a good way to gage your understanding of Javascript execution.
// Create a set of numbers from 0 and < count
// With a odds for any number occurring to be odds
// return as a new set;
function createLargeSet(count,odds){
var numbers = new Set();
while(count-- > 0){
if(Math.random() < odds){
numbers.add(count);
}
}
return numbers;
}
// create a array of large sets
function bigArrayOfSets(setCount,setMaxSize,odds){
var bigSets = [];
for(var i = 0; i < setCount; i ++){
bigSets.push(createLargeSet(setMaxSize,odds));
}
return bigSets;
}
function intersectA(firstSet,...sets) {
var count = sets.length;
var result = new Set(firstSet); // Only create one copy of the set
firstSet.forEach(item => {
var i = count;
var allHave = true;
while(i--){
allHave = sets[i].has(item)
if(!allHave) { break } // loop only until item fails test
}
if(!allHave){
result.delete(item); // remove item from set rather than
// create a whole new set
}
})
return result;
}
function intersectB(firstSet, ...sets) {
var intersect = (a,b) => {
return new Set([...a].filter(item => b.has(item)))
};
sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
return firstSet;
}
var testSets = [];
for(var i = 0.1; i <= 1; i += 0.1){
testSets.push(bigArrayOfSets(100,10000,i));
}
var now = performance.now();
testSets.forEach(testDat => intersectB(...testDat));
var time = performance.now() - now;
console.log("Execution time 'intersectB' : " + time);
var now = performance.now();
testSets.forEach(testDat => intersectA(...testDat));
var time = performance.now() - now;
console.log("Execution time 'intersectA' : " + time);
As you can see using a simple while loop may not be a cool as using filter but the performance benefit is huge, and something to keep in mind next time you are writing that perfect 3 line ES6 array manipulation function. Dont forget about for and while.
The most efficient algorithm for intersecting n arrays is the one implemented in fast_array_intersect. It runs in O(n), where n is the total number of elements in all the arrays.
The base principle is simple: iterate over all the arrays, storing the number of times you see each element in a map. Then filter the smallest array, to return only the elements that have been seen in all the arrays. (source code).
You can use the library with a simple :
import intersect from 'fast_array_intersect'
intersect([[1,2,3], [1,2,6]]) // --> [1,2]
OK i guess the most efficient way of performing the Array intersection is by utilizing a Map or Hash object. Here I test 1000 arrays each with ~1000 random integer items among 1..175 for an intersection. The result is obtained in less than 100msec.
function setIntersection(a){
var m = new Map(),
r = new Set(),
l = a.length;
a.forEach(sa => new Set(sa).forEach(n => m.has(n) ? m.set(n,m.get(n)+1)
: m.set(n,1)));
m.forEach((v,k) => v === l && r.add(k));
return r;
}
var testSets = Array(1000).fill().map(_ => Array(1000).fill().map(_ => ~~(Math.random()*175+1)));
console.time("int");
result = setIntersection(testSets);
console.timeEnd("int");
console.log(JSON.stringify([...result]));

Javascript fill array with intermediate value

I'm trying to fill an array with missing intermediate data
My data input is like this
var data = [[5.23,7],[5.28,7],[5.32,8],[5.35,8]];
I wanna fill the array with missing value but I need to respect this rule:
The 1st value on 2d array must be the next sequence number, so 5.23
... 5.24 ... 5.25 ...
The 2nd value on 2d array must be the same element from the i+1
value
So the results in this case would be
var data = [[5.23,7],[5.24,7],[5.25,7],[5.26,7],[5.27,7],[5.28,7],[5.29,8],[5.30,8],[5.31,8],[5.32,8],[5.33,8],[5.34,8],[5.35,8]];
This little piece of code works, but I don't know
how to put in loop
and how to write a while loop that pass every time the new length of the array
var data = [[5.23,7],[5.28,7],[5.32,8],[5.35,8]];
if (data[1][0]-data[0][0] > 0.01) {
data.push([data[0][0]+0.01,data[1][1]]);
data.sort(function (a, b) { return a[0] - b[0]; });
} else {
check the next element
}
console.log(data);
Any idea?
Here's another idea... I thought it might feel more natural to loop through the sequence numbers directly.
Your final array will range (in this example) from 5.23 to 5.35 incrementing by 0.01. This approach uses a for loop starting going from 5.23 to 5.35 incrementing by 0.01.
key points
Rounding: Work in x100 then divide back down to avoid floating point rounding issues. I round to the neared hundredth using toFixed(2) and then converting back to a number (with leading + operator).
Indexing: Recognizing 5.23 is the zero index with each index incrementing 1/100, you can calculate index from numerical values, ex. 100*(5.31-5.23) equals 8 (so 5.31 belongs in output[8]).
2nd values: given a numerical value (ex. 5.31), just find the first element in the data array with a higher 1st value and use its 2nd value - this is a corollary of your requirement. Because 5.31 <= 5.28 is false, don't use 7 (from [5.28,7]). Because 5.31 <= 5.32 is true, use 8 (from [5.32,8]).
EDIT
I improved the performance a bit - (1) initialize output instead of modifying array size, (2) work in multiples of 100 instead of continuously rounding from floating point to hundredths.
I ran 5000 iterations on a longer example and, on average, these modifications make this approach 3x faster than Redu's (where the original was 2x slower).
var data = [[5.23,7],[5.28,7],[5.32,8],[5.35,8]];
var output = Array((data[data.length-1][0]-data[0][0]).toFixed(2)*100+1)
function getIndex(value){
return (value-data[0][0]*100)
}
for( var i = 100*data[0][0]; i <= 100*data[data.length-1][0]; i++ ){
output[getIndex(i)] = [i/100, data.find( d => i <= 100*d[0] )[1]]
}
//console.log(output)
// Performance comparison
function option1(data){
let t = performance.now()
var output = Array((data[data.length-1][0]-data[0][0]).toFixed(2)*100+1)
function getIndex(value){
return (value-data[0][0]*100)
}
for( var i = 100*data[0][0]; i <= 100*data[data.length-1][0]; i++ ){
output[getIndex(i)] = [i/100, data.find( d => i <= 100*d[0] )[1]]
}
return performance.now()-t
}
function option2(data){
let t = performance.now()
newData = data.reduce((p,c,i,a) => i ? p.concat(Array(Math.round(c[0]*100 - a[i-1][0]*100)).fill()
.map((_,j) => [Number((a[i-1][0]+(j+1)/100).toFixed(2)),c[1]]))
: [c],[]);
return performance.now()-t
}
var testdata = [[1.13,4],[2.05,6],[5.23,7],[5.28,7],[5.32,8],[5.35,8],[8.91,9],[10.31,9]];
var nTrials = 10000;
for(var trial=0, t1=0; trial<=nTrials; trial++) t1 += option1(testdata)
for(var trial=0, t2=0; trial<=nTrials; trial++) t2 += option2(testdata)
console.log(t1/nTrials) // ~0.4 ms
console.log(t2/nTrials) // ~0.55 ms
Array.prototype.reduce() is sometimes handy to extend the array. May be you can do as follows;
var data = [[5.23,7],[5.28,7],[5.32,8],[5.35,8]],
newData = data.reduce((p,c,i,a) => i ? p.concat(Array(Math.round(c[0]*100 - a[i-1][0]*100)).fill()
.map((_,j) => [Number((a[i-1][0]+(j+1)/100).toFixed(2)),c[1]]))
: [c],[]);
console.log(newData);
var data = [[1.01,3],[1.04,4],[1.09,5],[1.10,6],[1.15,7]],
newData = data.reduce((p,c,i,a) => i ? p.concat(Array(Math.round(c[0]*100 - a[i-1][0]*100)).fill()
.map((_,j) => [Number((a[i-1][0]+(j+1)/100).toFixed(2)),c[1]]))
: [c],[]);
console.log(newData);
I propose this solution :
var data = [[5.23,7],[5.28,7],[5.32,8],[5.35,8]];
var res = [];
data.forEach((item, index, arr) => {
res.push(item);
var temp = item[0];
while (arr[index+1] && arr[index+1][0]-temp > 0.01){
temp += 0.01;
res.push([temp, arr[index+1][1]]);
}
});
console.log(res);

find all possible combinations of N non-repeating numbers within a certain range that add up to X

i have been trying to find a solution to this for several months now. it is for an art project of mine. so far i could find partial python and c solutions, but they are of no use for my case... i need a working solution either in PHP or Javascript.
this is the question:
find all possible combinations of N numbers, the following should be satisfied:
numbers are not repeated within a combination
numbers are not repeated in other solutions in different order
only whole numbers are being used
within a certain range of whole numbers
that add up to X
for example:
find all combinations of 3 numbers
within all numbers from 1-12
that add up to 15
the computed solution should spit out:
[1,2,12]
[1,3,11]
[1,4,10]
[1,5,9]
[1,6,8]
[1,7,7] = EXAMPLE OF WRONG OUTPUT, NO REPEATING NUMBERS WITHIN COMBINATION
[1,8,6] = EXAMPLE OF WRONG OUTPUT, NO REPEATING NUMBERS IN OTHER SOLUTIONS (see [1,6,8])
[2,3,10]
[2,4,9]
[2,5,8]
[2,6,7]
[3,4,8]
[3,5,7]
[4,5,6]
obviously that was easy to do in a couple of minutes by hand, but i need to calculate a much bigger range and much more numbers, so i need a short script to do this for me...
any help would be appreciated!
I feel like the most elegant way to handle this challenge is via recursion.
function getCombos(target, min, max, n) {
var arrs = [];
if (n === 1 && target <= max) {
arrs.push([target]);
} else {
for (var i = min; i < target / n && i <= max; i++) {
var arrays = getCombos(target - i, i + 1, max, n - 1);
for (var j = 0; j < arrays.length; j++) {
var array = arrays[j];
array.splice(0, 0, i);
arrs.push(array);
}
}
}
return arrs;
}
Explanation
This works by climbing up from the minimum number i as the first item in each array, and passing the remainder (target-i) back into the recursive function to be split into n-1 components, with the minimum increased by one with each recursive call.
15 = (1 + 14) = 1 + (2 + 12)
15 = (1 + 14) = 1 + (3 + 11)
15 = (1 + 14) = 1 + (4 + 10)
...
15 = (1 + 14) = 1 + (6 + 8)
15 = (2 + 13) = 2 + (3 + 10)
15 = (2 + 13) = 2 + (4 + 9)
...
15 = (4 + 11) = 4 + (5 + 6)
Note that the numbers at the first index of each array will never exceed target/n, where target is the number you're summing to, and n is the number of items in the array. (So when splitting 15 into 3 components, the first column will always be less than 5.) This holds true for the other columns as well, but n is reduced by 1 as the index of the array climbs. Knowing this allows us to recurse without requiring extra parameters on our recursive function.
Working Example
Check out the snippet below to see it in action.
function getCombos(target, min, max, n) {
var arrs = [];
if (n === 1 && target <= max) {
arrs.push([target]);
} else {
for (var i = min; i < target / n && i <= max; i++) {
var nextTarget = target - i;
var nextMin = i + 1;
var arrays = getCombos(nextTarget, nextMin, max, n - 1);
for (var j = 0; j < arrays.length; j++) {
var array = arrays[j];
array.splice(0, 0, i);
arrs.push(array);
}
}
}
return arrs;
}
document.getElementById("submit").onclick = function () {
var target = document.getElementById("target").value;
var min = document.getElementById("min").value;
var max = document.getElementById("max").value;
var n = document.getElementById("n").value;
var result = getCombos(+target, +min, +max, +n);
document.getElementById("output").innerHTML = result.join("<br/>");
};
.table {
display:table;
table-layout:fixed;
width:100%;
}
.table-row {
display:table-row;
}
.cell {
display:table-cell;
}
<div class="table">
<div class="table-row">
<div class="cell">Target:</div>
<div class="cell">
<input id="target" type="text" value=15>
</div>
<div class="cell">n:</div>
<div class="cell">
<input id="n" type="text" value=3>
</div>
</div>
<div class="table-row">
<div class="cell">Min:</div>
<div class="cell">
<input id="min" type="text" value=1>
</div>
<div class="cell">Max:</div>
<div class="cell">
<input id="max" type="text" value=12>
</div>
</div>
</div>
<input id="submit" type="button" value="submit" />
<div id="output" />
If you generate the lists in ascending order, you will avoid both kinds of repetition.
An easy recursive solution consists of selecting each possible first element, and then recursively calling the generator requesting the possible continuations: that is, the continuations are restricted to having one fewer element, to starting with a value greater than the chosen element, and summing to the desired sum minus the chosen element.
Partitions(min, size, total):
if size is 1:
if total < min: return nothing
else return the list [total]
for each value i between min and total:
get the set of lists Partitions(i+1, size-1, total-i)
add i to the beginning of each list
return all the lists.
The above can be improved by not letting i get beyond the largest practical value, or at least beyond a conservative estimate. Alternatively, you can stop incrementing i after a recursive call returns an empty set.
Below is a recursive function that does what you want.
For your example, you would call it like this:
combos(3, 1, 12, 15);
The additional function parameters (a, running, current) keep track of the current state and can be ignored:
var arr= [];
function combos(num, min, max, sum, a, running, current) {
var i;
a= a || [];
running= running || 0;
current= current || min;
for(i = current ; i <= max ; i++) {
if(num===1) {
if(i+running===sum) {
arr.push(a.concat(i));
}
}
else {
combos(num-1, min, max, sum, a.concat(i), i+running, i+1);
}
}
};
Fiddle
Here's a slightly optimized solution. By iterating from largest to smallest in the range, it becomes pretty easy to skip all the possibilities that are too large.
function combos(size, start, end, total, solution) {
var solutions = [];
solution = solution || [];
if (size === 1) {
if (start <= total && end >= total) {
solutions.push(solution.concat([total]));
}
return solutions;
} else {
while (end > start) {
var newTotal = total - end;
solutions = solutions.concat(
combos(
size - 1,
start,
Math.min(end - 1, newTotal),
newTotal,
solution.concat([end])
)
);
end--;
}
return solutions;
}
}
Might not be efficient for large numbers, but using 3 nested for() loops you can do -
$t=20; // up to X
$s=$t-3; // sets inner loop max
$r=$t/3; // sets middle loop max
$q=$r-1; // sets outer loop max
$results= array(); // array to hold results
for($x=1;$x<=$q;$x++){
for($y=($x+1);$y<=$r;$y++){
for($z=($x+2);$z<=$s;$z++){
// if sum == max && none are the same value
if(($x+$y+$z)==$t && ($x!=$y && $x!=$z && $y!=$z)){
$results[]=array($x,$y,$z);
}
}
}
}

Categories

Resources