I have a set of N arrays that update at various frequencies (data is pushed into them). If I have arrays that update at slower frequencies than the "fastest" arrays, those slower arrays should be padding with the previous data.
Example: 2 arrays, updating at different frequencies, over 10 seconds would look like
// fast array updates every 1 second
// after 10 seconds the data is:
let fast_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// slow array updates every 5 seconds
// after 10 seconds the data is:
let slow_array = [0, 1];
I would like the slower arrays to be padded as such
fast_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
slow_array = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
And the array lengths should always be equal.
I have written an entire testable setup for this, and just cannot find out that magical formula to pad properly. Please take a look at the fiddle for an easy way to solve this! Look at function "processor"
HTML
<div>
<button id="start" type="button">
Start
</button>
<button id="stop" type="button">
Stop
</button>
</div>
<div id="feature1">
<div>
Length: <span id="feature1len"></span>
</div>
<div>
[<span id="feature1data"></span>]
</div>
</div>
<div id="feature2">
<div>
Length: <span id="feature2len"></span>
</div>
[<span id="feature2data"></span>]
</div>
</div>
JS
let startbutton = document.getElementById('start');
let stopbutton = document.getElementById('stop');
startbutton.addEventListener('click', () => {
start();
});
stopbutton.addEventListener('click', () => {
stop();
});
let feature1 = {
freq: 1,
raw_data: [],
final_data: [],
interval: null,
lenHtml: document.getElementById('feature1len'),
dataHtml: document.getElementById('feature1data')
}
let feature2 = {
freq: 5,
raw_data: [],
final_data: [],
interval: null,
lenHtml: document.getElementById('feature2len'),
dataHtml: document.getElementById('feature2data')
}
let render_interval = null;
function getRandomInt(min = 0, max = 100) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function render() {
processor(feature1);
processor(feature2);
feature1.lenHtml.innerText = feature1.final_data.length;
feature1.dataHtml.innerText = feature1.final_data.toString();
feature2.lenHtml.innerText = feature2.final_data.length;
feature2.dataHtml.innerText = feature2.final_data.toString();
}
function start() {
feature1.raw_data = [];
feature1.final_data = [];
feature2.raw_data = [];
feature2.final_data = [];
feature1.raw_data.push(getRandomInt())
feature1.interval = setInterval(() => {
feature1.raw_data.push(getRandomInt())
}, feature1.freq * 1000);
feature2.raw_data.push(getRandomInt())
feature2.interval = setInterval(() => {
feature2.raw_data.push(getRandomInt())
}, feature2.freq * 1000);
render_interval = setInterval(() => {
render();
}, 1000)
render();
}
function stop() {
clearInterval(feature1.interval);
clearInterval(feature2.interval);
clearInterval(render_interval);
}
function processor(feature) {
// determine highest frequency
let most_frequent = Math.min(feature1.freq, feature2.freq);
// determine longest length
let longest_length = Math.max(feature1.raw_data.length, feature2.raw_data.length);
// process data if needed
feature.final_data = [];
for (let i = 0; i < feature.raw_data.length; i++) {
feature.final_data.push(feature.raw_data[i]);
if(feature.freq !== most_frequent){
let max_filler = 0; //???
for(let x = 0; x < max_filler; x++){
feature.final_data.push(feature.raw_data[i]);
}
}
}
}
render();
https://jsfiddle.net/79wbnkf8/1/
You can add functions:
function lcm(x, y) {
return (!x || !y) ? 0 : Math.abs((x * y) / gcd(x, y));
}
function gcd(x, y) {
x = Math.abs(x);
y = Math.abs(y);
while(y) {
var t = y;
y = x % y;
x = t;
}
return x;
}
and then modify your process function like this:
function processor(feature) {
// determine highest frequency
let most_frequent = Math.min(feature1.freq, feature2.freq);
// determine longest length
let longest_length = Math.max(feature1.raw_data.length, feature2.raw_data.length);
let l = lcm(feature1.freq, feature2.freq);
let max_filler = l / feature1.freq;
// process data if needed
feature.final_data = [];
for (let i = 0; i < feature.raw_data.length; i++) {
if(feature.freq !== most_frequent){
//let max_filler = 0; //???
for(let x = 0; x < max_filler; x++){
feature.final_data.push(feature.raw_data[i]);
if (feature.final_data.length >=longest_length) break;
}
} else {
feature.final_data.push(feature.raw_data[i]);
}
}
}
Find the most frequent feature from your feature list
let mostFreqFeature = features.reduce((min, feature) => min.freq < feature.freq ? min : feature);
Set the interval for most frequent feature in the start function
pushItem_interval = setInterval(() => {
pushItem();
}, mostFreqFeature.freq * 1000)
Finally push items into your list
function pushItem(){
for(var i=0;i<features.length;i++){
let feature = features[i];
if(feature.freq==mostFreqFeature.freq)
feature.raw_data.push(getRandomInt())
else{
if(feature.raw_data.length<=0){
feature.raw_data.push(getRandomInt());
return;
}
if((feature.raw_data.length)% feature.freq==0)
feature.raw_data.push(getRandomInt())
else{
let lastItem = feature.raw_data[feature.raw_data.length-1];
feature.raw_data.push(lastItem)
}
}
}
}
working example jsfiddle
Hope, it will helpful for your need.
Related
I have an array with names
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
const teams = players.length / 2; // -> 4 teams
I want to make teams of 2 people (randomly chosen) and make new arrays from the results -> team
function createTeams() {
for (let i = 0; i < teams; i++) {
for (let x = 0; x < 2; x++) {
// get a random player
selectedPlayer = players[Math.floor(Math.random() * players.length)];
console.log('Selected Player will be added to a team: ', selectedPlayer);
// delete this player from the array
while (players.indexOf(selectedPlayer) !== -1) {
players.splice(players.indexOf(selectedPlayer), 1);
}
// add this player to a new array
//?????
}
}
}
Anyone knows how to do this?
function pickTeams(array, teamSize) {
// Shuffle array, make a copy to not alter original our original array
const shuffled = [...array].sort( () => 0.5 - Math.random());
const teams = [];
// Create teams according to a team size
for (let i = 0; i < shuffled.length; i += teamSize) {
const chunk = shuffled.slice(i, i + teamSize);
teams.push(chunk);
}
return teams;
}
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
const teams = pickTeams(players, 2);
You can define a new Array which will contains the teams where you push the two players.
Note that it's better to pass the players array as a parameters of the function and make a swallow copy of it so it won't modify the player array.
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
function createTeams(players) {
const playersLeft = [...players]
const newTeams = []
const nbTeams = players.length / 2
for (let i=0; i<nbTeams; i++){
const player1 = playersLeft.splice(Math.floor(Math.random()*playersLeft.length),1)[0]
const player2 = playersLeft.splice(Math.floor(Math.random()*playersLeft.length),1)[0]
newTeams.push([player1, player2])
}
return newTeams
}
console.log(createTeams(players))
Edit : Improve version using a nbPlayerPerTeam parameter
const players = ['name1', 'name2', 'name3', 'name4', 'name5', 'name6', 'name7', 'name8'];
function createTeams(players, nbPlayerPerTeam) {
const playersLeft = [...players]
const newTeams = []
const nbTeams = players.length / nbPlayerPerTeam
for (let i = 0; i < nbTeams; i++) {
const players = []
for (let j = 0; j < nbPlayerPerTeam; j++) {
const player = playersLeft.splice(Math.floor(Math.random() * playersLeft.length), 1)[0]
players.push(player)
}
newTeams.push(players)
}
return newTeams
}
console.log(createTeams(players, 3))
const players = ['name1', 'name2', 'name3', 'name4', 'name5', 'name6', 'name7', 'name8'];
// Divide data by length
const divide = (arr, n) => arr.reduce((r, e, i) =>
(i % n ? r[r.length - 1].push(e) : r.push([e])) && r, []);
const shuffle = (arr) => {
// Deep copy an array using structuredClone
const array = structuredClone(arr);
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle
while (currentIndex != 0) {
// Pick a remaining element
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]
];
}
return array;
}
// Shuffle players
const shuffled = shuffle(players);
// Set a number of people in a team
const groups = divide(shuffled, players.length / 2);
groups.forEach((team, i) => console.log(`team ${i+1}`, JSON.stringify(team)));
Check this updated code, I hope it will work for you. Just create two dimensional array and push players.
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
let multi_team=new Array; // new array for team of random 2 players
const teams = players.length / 2; // -> 4 teams
console.log(teams);
createTeams();
function createTeams() {
for (let i = 0; i < teams; i++) {
multi_team[i]= new Array;
for (let x = 0; x < 2; x++) {
// get a random player
selectedPlayer = players[Math.floor(Math.random() * players.length)];
multi_team[i][x]=selectedPlayer;
// delete this player from the array
while (players.indexOf(selectedPlayer) !== -1) {
players.splice(players.indexOf(selectedPlayer), 1);
}
}
}
console.log(multi_team);
}
To differ from previous answers, this is a dynamic way to do it, so you don't care if there are 5 teams or 2, 10 players or 500.
const players = ['j1', 'j2', 'j3', 'j4', 'j5', 'j6', 'j7', 'j8'];
const organizeTeams = (numberOfTeams, players) => {
var teams = [];
// Clone the array to avoid mutations
const freePlayers = [...players];
// How many players will play per team
const playersPerTeam = Math.floor(players.length / numberOfTeams);
// How many player won't have team
const unorganizablePlayers = freePlayers.length % numberOfTeams;
for (let t = 1; t <= numberOfTeams; t++) {
teams = [...teams, pickRandomPlayers(freePlayers, playersPerTeam)];
}
return { teams, playersPerTeam, unorganizablePlayers };
};
const pickRandomPlayers = (players, playersPerTeam) => {
let pickedPlayers = [];
for (let c = 1; c <= playersPerTeam; c++) {
const index = Math.floor(Math.random() * (players.length - 1));
const player = players[index];
pickedPlayers = [...pickedPlayers, player];
// When we pick the player we remove it from the array to avoid duplicates.
players.splice(index, 1);
}
return pickedPlayers;
};
const championship = organizeTeams(3, players);
console.log(`We have ${championship.teams.length} teams.`);
championship.teams.forEach((team, index) => {
console.log(`Team ${index + 1} players: ${team.join(',')}`);
});
console.log(
`There was no place to assign ${championship.unorganizablePlayers} players`
);
The OP needs (to implement ) a reliable shuffle and likewise an array's chunk functionality.
Then one just has to shuffle the provided players array (or a shallow copy of the latter) and to divide the array of randomly ordered player items into chunks, each of custom provided length.
function shuffleArray(arr) {
let idx;
let count = arr.length;
while (--count) {
idx = Math.floor(Math.random() * (count + 1));
[arr[idx], arr[count]] = [arr[count], arr[idx]];
}
return arr;
}
function createListOfChunkLists(arr, chunkLength = 0) {
chunkLength = Math.max(0, chunkLength);
return (chunkLength === 0 && []) ||
arr.reduce((list, item, idx) => {
if (idx % chunkLength === 0) {
list.push([ item ]);
} else {
list[list.length - 1].push(item);
// `at` still not supported by safari.
// list.at(-1).push(item);
}
return list;
}, []);
}
// ... OP ... I have an array with names
const players = ['name1', 'name2', 'name3', 'name4', 'name5', 'name6', 'name7', 'name8'];
// ... OP ... I want to make teams of 2 people (randomly chosen)
// and make new arrays from the results -> team
const teams = createListOfChunkLists(
shuffleArray([...players]), 2
);
console.log({ teams, players });
console.log({
teams: createListOfChunkLists(
shuffleArray([...players]), 2
),
players
});
console.log({
teams: createListOfChunkLists(
shuffleArray([...players]), 4
),
players
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
I want to compare single paths of a decision tree in one function in javascript.
I only know how to do this, as the nodes are adding up but thats just comparing the nodes.
Instead I want to look at one path and then look at the next one.
for example: next decision should be in range of -3. given are the numbers [9, 8, 6, 5, 3, 2, 0]. The sum of the list is the value. The path with a length of 6 with the biggest value is wanted.
2 - 0
3 <
5< 0
/ 2 - 0
6
/ \ 2 - 0
3<
0
8 2 - 0
\ 3<
/ 5 < 0
2 - 0
9 2 - 0
3 <
5< 0
\ / 2 - 0
6
\ 2 - 0
3<
0
I want to compare those paths:
[9,6,3,0],
[9,6,3,2,0],
[9,6,5,2,0],
[9,6,5,3,0],
[9,6,5,3,2,0],
[9,8,6,3,0],
[9,8,6,3,2,0],
[9,8,6,5,2,0],
[9,8,6,5,3,0],
[9,8,6,5,3,2,0],
and instead of calculating every path and then comparing them simultaneously, I want to only calculate the first path and then compare it with the second path like this:
[9,6,3,0], //value 18
[9,6,3,2,0]//value 21
|| which one has the nearest length of 6 with the most Value?
\/
[9,6,3,2,0]//value 21
[9,6,3,2,0],//value 21
[9,6,5,2,0] //value 23
|| which one has the nearest length of 6 with the most Value?
\/
[9,6,5,2,0] //value 23
... and so on
how to compare only 2 paths at a time instead of every ?
Edit 1 :
My Code so far: https://replit.com/#RubyKanima/BestSumwithlength#index.js
const numbers = [9, 8, 6, 5, 3, 2, 0];
const starting_node = Math.max(...numbers) + 3;
const path = (current_node, memo ={}) => {
if (current_node in memo) return memo[current_node];
if (current_node == 0) return [[]];
let paths = [];
let tmp_nodes = [];
for (n of numbers) {
if (n >= current_node - 3 && n < current_node) {
tmp_nodes.push(n);
}
}
for (let tmp of tmp_nodes) {
const tmp_path = path(tmp);
const tmp_paths = tmp_path.map(way => [tmp, ...way]);
paths.push(...tmp_paths);
}
memo[current_node] = paths;
paths = paths.filter(x => x.length <= 6);
return paths;
};
const bestPath = all_paths => {
all_paths = all_paths.filter(x => (x.length = 6));
let bestValue = 0;
let bestPath = null;
for (let path of all_paths) {
let value = 0;
for (node of path) value += node;
if (value > bestValue) {
bestValue = value;
bestPath = path;
}
}
return bestPath;
};
const path_array = path(starting_node);
console.log(bestPath(path_array));
It does the job, but if I get over a thousand numbers it gets a stack overflow. (In the example I reduced some numbers to make it easier to understand, in reality the range is -360 and not -3)
Biggest Problem: too much data
How to solve it?: Comparing only 2 Paths at a time and then calculating the next Path.
What I want to know: How to only calculate 2 Paths.
So I wrote a merge sort algorithm and I added some performance time calculation for both your solution and the merge sort solution. In this scenario, it seems the merge sort performs better. It compares two arrays at the time, until it reaches the one you need. Try this on your data, which should be bigger and see if it works better.
const numbers = [9, 8, 6, 5, 3, 2, 0];
const starting_node = Math.max(...numbers) + 3;
const path = (current_node, memo ={}) => {
if (current_node in memo) return memo[current_node];
if (current_node == 0) return [[]];
let paths = [];
let tmp_nodes = [];
for (n of numbers) {
if (n >= current_node - 3 && n < current_node) {
tmp_nodes.push(n);
}
}
for (let tmp of tmp_nodes) {
const tmp_path = path(tmp);
const tmp_paths = tmp_path.map(way => [tmp, ...way]);
paths.push(...tmp_paths);
}
memo[current_node] = paths;
paths = paths.filter(x => x.length <= 6);
return paths;
};
const bestPath = all_paths => {
all_paths = all_paths.filter(x => (x.length = 6));
let bestValue = 0;
let bestPath = null;
for (let path of all_paths) {
let value = 0;
for (node of path) value += node;
if (value > bestValue) {
bestValue = value;
bestPath = path;
}
}
return bestPath;
};
//-----merge sort algorithm---------
const sumArr = arr => arr.reduce((acc, next)=> acc+next)
function merge(arr1, arr2){
if (sumArr(arr1) > sumArr(arr2)) {
return arr1
} else if(sumArr(arr1) < sumArr(arr2)) {
return arr2
} else {return arr1}
}
function mergeSort(arr){
if(arr.length <= 1) return arr[0];
let mid = Math.floor(arr.length/2);
let left = mergeSort(arr.slice(0,mid));
let right = mergeSort(arr.slice(mid));
return merge(left, right);
}
//-----end of merge sort algorithm------
const path_array = path(starting_node);
const start = performance.now()
console.log(bestPath(path_array));
const end = performance.now()
console.log("bestPath performance ", end-start)
const start2 = performance.now()
console.log(mergeSort(path_array))
const end2 = performance.now()
console.log("mergeSort performance ", end2-start2)
I have an array of object with each holding rgb values and I want to get the average of channel.
[{ r: 234, g: 250, b: 0 }, { r: 234, g: 250, b: 0 }, { r: 234, g: 250, b: 0 }]
The straight forward method is to map through the array, get the sum of each of the r, g and b values, then divide each by the length of the array.
const arrLength = colorBlock.length
let redArr = colorBlock.map(obj => obj.r)
let greenArr = colorBlock.map(obj => obj.g)
let blueArr = colorBlock.map(obj => obj.b)
const add = (total, num) => total + num;
const totalR = redArr.reduce(add, 0);
const totalG = greenArr.reduce(add, 0);
const totalB = blueArr.reduce(add, 0);
const averageR = parseInt(totalR / arrLength)
const averageG = parseInt(totalG / arrLength)
const averageB = parseInt(totalB / arrLength)
My problem with this is that it's very slow when I have a big color block, a 900 x 900 block took about 5 seconds. Is there a more efficient way to do this?
EDIT: Sorry guy I make a mistake, the causes for my slow down actually came from the function that create the color block, not the average calculation. The calculation only took a few hundred millisecond.
Drop the separation of channels. Traversing one existing array once is likely more efficient than creating and filling 3 new arrays (which itself means traversing the original array 3 times), and then traversing all of them again.
let totals=colorBlock.reduce(
(totals,current)=>{
totals[0]+=current.r;
totals[1]+=current.g;
totals[2]+=current.b;
return totals;
},[0,0,0]);
let averageR=totals[0]/totals.length;
let averageG=totals[1]/totals.length;
let averageB=totals[2]/totals.length;
Your approach has the optimal time complexity. You could gain a factor of speed by avoiding callback functions, and relying on the plain old for loop (not the in or of variant, but the plain one):
const arrLength = colorBlock.length;
let totalR = 0, totalG = 0, totalB = 0;
for (let i = 0; i < arrLength; i++) {
let rgb = colorBlock[i];
totalR += rgb.r;
totalG += rgb.g;
totalB += rgb.b;
}
const averageR = Math.floor(totalR / arrLength);
const averageG = Math.floor(totalG / arrLength);
const averageB = Math.floor(totalB / arrLength);
You may at most halve the time tp process a 900x900 input with this, but that's about it.
To really improve more, you will need to rely on some heuristic that says that most of the time neighboring pixels will have about the same color code. And so you would then skip pixels. That will give you an estimated average, but that might be good enough for your purposes.
Remark: don't use parseInt when the argument is numeric. This will unnecessarily convert the argument to string, only to convert it back to number. Instead use Math.floor.
Use Array.reduce() on the entire object all at once, then Array.map() to calculate the averages:
const obj = [{ r: 200, g: 161, b: 1 }, { r: 50, g: 0, b: 3 }, { r: 50, g: 0, b: 5 }]
const res = obj.reduce((acc,cur) => {
acc[0] += cur.r
acc[1] += cur.g
acc[2] += cur.b
return acc
},[0,0,0]).map(cur => Math.round(cur / obj.length))
console.log(res)
Using a simple for loop instead of map and reduce can save quite a bit of time.
//Generate random colors
function genColor() {
return Math.floor(Math.random() * 255);
}
const SIZE = 500000;
const colorBlock = new Array(SIZE);
for(let i = 0; i < SIZE; i++) {
colorBlock[i] = {r:genColor(), g:genColor(), b:genColor()};
}
const arrLength = colorBlock.length;
var startTime0 = performance.now();
let redArr0 = colorBlock.map(obj => obj.r)
let greenArr0 = colorBlock.map(obj => obj.g)
let blueArr0 = colorBlock.map(obj => obj.b)
const add = (total, num) => total + num;
const totalR0 = redArr0.reduce(add, 0);
const totalG0 = greenArr0.reduce(add, 0);
const totalB0 = blueArr0.reduce(add, 0);
const averageR0 = Math.floor(totalR0 / arrLength)
const averageG0 = Math.floor(totalG0 / arrLength)
const averageB0 = Math.floor(totalB0 / arrLength)
var endTime0 = performance.now();
var totalR1 = 0;
var totalG1 = 0;
var totalB1 = 0;
for(let i = 0; i < SIZE; i++) {
totalR1 += colorBlock[i].r;
totalG1 += colorBlock[i].g;
totalB1 += colorBlock[i].b;
}
const averageR1 = Math.floor(totalR1 / arrLength)
const averageG1 = Math.floor(totalG1 / arrLength)
const averageB1 = Math.floor(totalB1 / arrLength)
var endTime1 = performance.now();
console.log( averageR0, averageG0, averageB0)
console.log( averageR1, averageG1, averageB1)
console.log("Reduce", endTime0 - startTime0);
console.log("for loop", endTime1 - endTime0);
I'm trying to generate numbers that are equal 100(%) but all numbers have to be atleast 4 numbers from each other. So if I generate 4 numbers they have to be like this for example [22,28,15,35] and can't be like this [22,28,20,30] as difference between 28-30 and 22 - 20 is less than 4.
I was able to put together method for generating numbers that equal something which looks like this.
generate (max, many) {
this.numbers_array = []
this.normalized_numbers_array = []
this.sum = 0
for (let i = 0; i < many; i++) {
this.numbers_array.push(Math.random())
this.sum += this.numbers_array[i]
}
this.remaining = max
for (let i = 0; i < this.numbers_array.length; i++) {
this.outcome= (this.numbers_array[i] / this.sum) * max
this.normalized_numbers_array[i] = Math.floor(this.outcome)
this.remaining -= this.normalized_numbers_array[i]
if (i + 1 == this.numbers_array.length) {
while (this.remaining > 0) {
this.normalized_numbers_array[i]++
this.remaining--
}
}
}
}
And works fine. My next approach was trying to compare the normalized numbers with each other through two for loops and if statements. Then according to the differences between the numbers with each other I wanted add 2% to one and substruct 2% from other of the two numbers that are being compared. Then I put them in new array which I wanted to normalized again.
for (let i = 0; i < this.normalized_numbers_array.length; i++) {
for (let j = i + 1; j < this.normalized_numbers_array.length; j++) {
if (Math.abs(this.normalized_numbers_array[i] - this.normalized_numbers_array[j]) < 5) {
//do something
if (this.normalized_numbers_array[i] > this.normalized_numbers_array[j]) {
//do something
} else if (this.normalized_numbers_array[i] <= this.normalized_numbers_array[j]) {
//do something
}
}
}
}
This approach is flawed, though. As by adding or substructing I can make new differences that are less than 4. For example [35,18,26,21]->[35,16,26,23] Difference between 26 and 23 is 3 after the change.
My thought was to create another loop that would be going as long as there are differences. But I'm not sure that would work. So I was wondering whether there is better working solution for this problem- maybe being able to generate those numbers with differences in size from the start and not have to change them after.
Generate four uniform random numbers from the same range
Scale them so they add up to 76 (100 - separators, see below); adjust randomly as needed to account for the remainder
Insert separators: sort, then add 4 to the first, 8 to the second, 12 to the last one (and no adjustment to the zeroth).
Example
Generate [102, 387, 386, 284]
Scale: [102, 387, 386, 284] * 76 / (102 + 387 + 386 + 284) evaluates to [6, 25, 25, 18]
Adjust: That's only 74, so randomly add 1 to two elements: [6, 25, 26, 19]
Sort: [6, 19, 25, 26]
Insert separators: [6, 23, 33, 38]
Adds up to 100, guaranteed to be at least 4 apart, [EDIT] very few loops (to ensure no division by zero), and with as little arbitrary disturbance of the distribution. For the curious, here's what it looks like:
function f() {
let q, s;
while (!s) {
q = Array.from({length: 4}, () => Math.random());
s = q.reduce((a, e) => a + e);
}
q.forEach((e, i, q) => q[i] = (e * 76 / s)|0);
s = q.reduce((a, e) => a + e);
while (s < 76) {
q[(Math.random() * 4)|0]++; s++;
}
q.sort((a, b) => a - b);
q.forEach((e, i, q) => q[i] += i * 4);
return q;
}
const N = 100000;
function draw() {
const labels = ["#0", "#1", "#2", "#3", "Any"];
const colours = ["red", "orange", "green", "blue", "black"];
const data = Array.from({length:5}, (e, i) => ({
label: labels[i],
borderColor: colours[i],
backgroundColor: i == 4 ? "rgba(0, 0, 0, 0.1)" : "rgba(0, 0, 0, 0)",
pointRadius: 0,
data: Array.from({length:100}, (e, i) => ({ x: i, y: 0 }))
}));
for (let s = 0; s < N; s++) {
const q = f();
q.forEach((e, i) => {
data[i].data[e].y++;
data[4].data[e].y++;
});
}
const ctx = document.querySelector('canvas').getContext('2d');
const myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: data
},
options: {
maintainAspectRatio: false,
animation: false,
legend: {
position: 'right'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}],
xAxes: [{
type: 'linear',
ticks: {
beginAtZero: true,
max: 100
}
}]
}
}
});
};
draw();
document.querySelector('canvas').addEventListener('dblclick', draw);
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0/dist/Chart.min.js"></script>
<canvas width="400" height="180"/>
<!DOCTYPE html>
<html>
<head>
</head>
<style>
.HideX{
display:none;
};
</style>
<body>
<input type="text" id="QQ">
<input type="text" id="QQ2">
<script>
function isEmpty(map) {
for(var key in map) {
if (map.hasOwnProperty(key)) {
return false;
}
}
return true;
}
function GetRandom(St,Range){
return Math.floor((Math.random() * Range) + St);
}
function Pick4(){
var BigPool={};
var ResultX=[];
//Generate [1,100]
for(var i=1;i<=100;i++){
BigPool[i.toString()]=i;
}
var Remain=100;
var Last=100;
for(var j=0;j<3;j++){
if(isEmpty(BigPool)){//Althoght impossible but add this exception
return Pick4();
}
var Pick=GetRandom(1,Remain);
if(BigPool.hasOwnProperty(Pick.toString())){
delete BigPool[Pick.toString()];//Remove Pick
ResultX.push(Pick);
Remain-=Pick;
for(var i=Remain+1;i<=Last;i++){
if(BigPool.hasOwnProperty(i.toString())){
delete BigPool[i.toString()];//Remove above remain
}
}
Last=Remain;
}else{
j--;
continue;
}
for(var i=-3;i<=3;i++){//Remove [Pick-3,Pick+3]
if(BigPool.hasOwnProperty((Pick+i).toString())){
delete BigPool[(Pick+i).toString()];//Remove [Pick-3,Pick+3]
}
}
}
if(BigPool.hasOwnProperty(Remain.toString())){
ResultX.push(Remain);
}else{
return Pick4();
}
return ResultX;
}
var G=Pick4();
document.getElementById("QQ").value = G;
document.getElementById("QQ2").value = G.reduce((a, b) => a + b, 0);
</script>
</body>
</html>
This is simple answer
A small brute force approach. After running, take a random result from the array.
function get4() {
function iter(temp, sum) {
var i, s, t;
for (i = 0; i <= max; i++) {
s = sum + i;
if (s > max) return;
t = temp.concat(i);
if (t.length === 4) {
if (s === max) result.push(t.map((v, i) => v + 4 * i));
continue;
}
iter(t, s);
}
}
var result = [],
max = 76;
iter([], 0);
return result;
}
var result = get4();
console.log(result.length);
console.log(result.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here is stats driven approach to the problem, might be useful to look at the problem from different perspective. We will use Multinomial distribution, which has natural property of sum being always equal to n (100 in your case). One could play with probabilities in such way, that difference between sampled numbers is very probable to be bigger than 4. Because mean values for multinomial are n*pi, we space probabilities far apart. After sampling array is sorted and checked for differences. If they are close, we reject the sample and call for another draw. I use https://github.com/jacobmenick/sampling code for multinomial sampling, there are other libraries as well.
Code, Node 12.1, Windows 10 x64
var multinom = SJS.Multinomial(100, [.02, .17, .33, .48]); // far away spacing of means n*p_i
q = multinom.draw().sort((a, b) => a - b); // sorted sample from multinomial
m = [] // array of pairwise differences, with first element removed
for (var k = 0; k < q.length - 1; ++k) {
var curr = q[k];
var next = q[k + 1];
m.push(next - curr);
}
reject = m.some(el => el < 4); // check for pairwise distance, if true reject sample and get another one
s = q.reduce((a, b) => a + b, 0); // check for sum, should be always 100
console.log(q);
console.log(m);
console.log(reject);
console.log(s);
please can somebody help?
If i have a total or a sum for instance 91
How can I create an array of the least amount of elements needed to get to the total value?
[50, 20, 10 , 5, 3, 2, 1] totaling this array will provide 91.
I know how to perform the opposite function using reduce or like so:
<script>
var numbers = [65, 44, 12, 4];
function getSum(total, num) {
return total + num;
}
function myFunction(item) {
document.getElementById("demo").innerHTML = numbers.reduce(getSum);
}
</script>
Greedy algorithm
Here is a solution using greedy algorithm. Note that this solution will work correctly in case when all the smaller numbers are divisors of all the bigger numbers such as in case [50, 10, 5, 1]. (see dynamic algorithm below this one for solution that can handle any input)
50 mod 10 = 0
50 mod 5 = 0
50 mod 1 = 0
10 mod 5 = 0
10 mod 1 = 0
5 mod 1 = 0
const sum = xs => xs.reduce((acc, v) => acc + v, 0);
function pickSubset(options, total, currentPick) {
if (sum(currentPick) === total) { return currentPick; }
if (options.length === 0) { return null; }
const firstVal = options[0];
let res = null;
if (sum(currentPick) + firstVal > total) {
res = pickSubset(options.slice(1), total, currentPick);
} else {
let opt1 = pickSubset(options, total, currentPick.concat(options[0]));
let opt2 = pickSubset(options.slice(1), total, currentPick.concat(options[0]));
if (opt1 && opt2) {
opt1.length < opt2.length ? res = opt1 : res = opt2
} else if (opt1) {
res = opt1;
} else {
res = opt2;
}
}
return res;
}
const total = 73;
const options = [50, 25, 10, 5, 2, 1];
console.log(pickSubset(options, total, []));
To handle unsorted input you can wrap it in another function and sort it prior to passing it to the main function.
const sum = xs => xs.reduce((acc, v) => acc + v, 0);
function pickSubset(options, total, currentPick) {
const sortedOptions = options.sort((a, b) => b - a);
function _pickSubset(options, total, currentPick) {
if (sum(currentPick) === total) { return currentPick; }
if (options.length === 0) { return null; }
const firstVal = options[0];
let res = null;
if (sum(currentPick) + firstVal > total) {
res = pickSubset(options.slice(1), total, currentPick);
} else {
let opt1 = pickSubset(options, total, currentPick.concat(options[0]));
let opt2 = pickSubset(options.slice(1), total, currentPick.concat(options[0]));
if (opt1 && opt2) {
opt1.length < opt2.length ? res = opt1 : res = opt2
} else if (opt1) {
res = opt1;
} else {
res = opt2;
}
}
return res;
}
return _pickSubset(sortedOptions, total, currentPick);
}
const total = 73;
const options = [50, 25, 10, 5, 2, 1].reverse();
console.log(pickSubset(options, total, []));
Dynamic programming (bottom-up natural ordering approach)
This solution works correctly for any type of input.
function pickSubset(options, total) {
function _pickSubset(options, change, minNums, numsUsed) {
for (let i = 0; i < change + 1; i++) {
let count = i;
let newNum = 1;
let arr = options.filter(v => v <= i);
for (let j of arr) {
if (minNums[i - j] + 1 < count) {
count = minNums[i - j] + 1;
newNum = j;
}
}
minNums[i] = count;
numsUsed[i] = newNum;
}
return minNums[change];
}
function printNums(numsUsed, change) {
const res = [];
let num = change;
while (num > 0) {
let thisNum = numsUsed[num];
res.push(thisNum);
num = num - thisNum;
}
return res;
}
const numsUsed = [];
const numsCount = [];
_pickSubset(options, total, numsCount, numsUsed);
return printNums(numsUsed, total);
}
const options = [50, 10, 5, 2, 1];
console.log(pickSubset(options, 73));
Dynamic programming (top-down memoization approach)
// helper function that generates all the possible solutions
// meaning, all the possible ways in which we can pay the provided amount
// and caches those solutions;
// returns the number of possible solutions but that is not neccessary
// in this case
const _pickSubset = (toPay, options, currentPick, cache) => {
if (toPay < 0) { return 0; }
if (toPay === 0) {
cache.add(currentPick);
return 1;
}
if (options.length === 0) { return 0; }
return _pickSubset(toPay - options[0], options, currentPick.concat(options[0]), cache)
+ _pickSubset(toPay, options.slice(1), currentPick, cache);
};
// memoize only with respect to the first two arguments - toPay, bills
// the other two are not necessary in this case
const memoizeFirstTwoArgs = fn => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args.slice(0, 2));
if (cache.has(key)) { return cache.get(key); }
const res = fn(...args);
cache.set(key, res);
return res;
};
};
// uses memoized version of makeChange and provides cache to that function;
// after cache has been populated, by executing memoized version of makeChange,
// find the option with smallest length and return it
const pickSubset = (toPay, options) => {
const cache = new Set();
const memoizedPickSubset = memoizeFirstTwoArgs(_pickSubset);
memoizedPickSubset(toPay, options, [], cache);
let minLength = Infinity;
let resValues;
for (const value of cache) {
if (value.length < minLength) {
minLength = value.length;
resValues = value;
}
}
return resValues;
}
const options = [50, 25, 10, 5, 2, 1];
const toPay = 73;
console.log(pickSubset(toPay, options));