I wanted to write a simple function that allows to roll random item from list, i did it with this code:
this.resources = [false, 'nitrogen', 'silicon', 'cobalt', 'magnesium'];
this.assign_resource = function() {
var index = tools.rnd(0, this.resources.length - 1);
return this.resources[index];
};
But it doesn't play well, so i wanted to change it to different system that allows a list of items (including empty one) and it picks one at random but each one has different chance (for example this one has 10%, this one has 20%). Maybe someone could help me with this kind of function.
Edited -----
for example this could be new list:
this.resources = [
{ type: 'empty', chance: 30 },
{ type: 'nitrogen', chance: 10 },
{ type: 'silicon', chance: 20 },
{ type: 'cobalt', chance: 30 },
{ type: 'magnesium', chance: 10 }
];
How to use it now to make it happen properly?
Edited 2 -----
I am trying to figure out well done programming solution using math rather then simply duplicating items in array, answers presented in this topic are just work arounds to a problem.
I'd solve it by having an array of objects with a chance to be the result, totalling 1.0, then picking a random number between 0 and 1, and then iterating over the resources and check if adding it to a cumulative total includes your random number.
var resources = [
{ resource: false, chance: 0.2 },
{ resource: 'nitrogen', chance: 0.1 },
{ resource: 'silicon', chance: 0.2 },
{ resource: 'cobalt', chance: 0.45 },
{ resource: 'mangesium', chance: 0.05 }
];
function get_result(resouceList) {
//get our random from 0 to 1
var rnd = Math.random();
//initialise our cumulative percentage
var cumulativeChance = 0;
//iterate over our resources
for (var i = 0; i < resouceList.length; i++) {
//include current resource
cumulativeChance += resouceList[i].chance;
if (rnd < cumulativeChance)
return resouceList[i].resource;
}
return false;
}
//test
console.log(get_result(resources));
console.log(get_result(resources));
console.log(get_result(resources));
console.log(get_result(resources));
console.log(get_result(resources));
I would set it up so that only the actual resources are in your array and "empty" happens if the random roll falls outside of those.
this.resources = [
{ type: 'nitrogen', chance: 10 },
{ type: 'silicon', chance: 20 },
{ type: 'cobalt', chance: 30 },
{ type: 'magnesium', chance: 10 }
];
this.assign_resource = function() {
var rnd = Math.random();
var acc = 0;
for (var i=0, r; r = this.resources[i]; i++) {
acc += r.chance / 100;
if (rnd < acc) return r.type;
}
// rnd wasn't less than acc, so no resource was found
return 'empty';
}
You can do something like this.
Creating an array with the same value multiple times gives it a higher chance of being selected.
var resources = [{
type: 'empty',
chance: 30
},
{
type: 'nitrogen',
chance: 10
},
{
type: 'silicon',
chance: 20
},
{
type: 'cobalt',
chance: 30
},
{
type: 'magnesium',
chance: 10
}
];
function GetRandom(list) {
var array = [];
for (var i = 0; i < list.length; i++) {
var item = list[i];
var chance = item.chance / 10;
for (var j = 0; j < chance; j++) {
array.push(item.type);
}
}
var idx = Math.floor(Math.random() * array.length);
return array[idx];
}
console.log(GetRandom(resources))
.as-console-wrapper { max-height: 100% !important; top: 0; }
This is how I'd implement the solution.
Step 1: accumulate all the possible chances
Step 2: pick a random value in proportion to total chance
Step 3: loop through the resources to see in which part it random value falls under.
var resources = [
{ type: 'empty', chance: 30 },
{ type: 'nitrogen', chance: 10 },
{ type: 'silicon', chance: 20 },
{ type: 'cobalt', chance: 30 },
{ type: 'magnesium', chance: 10 }
];
function solution(resources) {
let chanceTotal = resources.reduce((acc, val) => { acc += val.chance ; return acc;}, 0);
let randomVal = parseInt(Math.random() * chanceTotal);
let chanceAcc = 0;
let ans;
resources.forEach(r => {
chanceAcc += r.chance;
if (chanceAcc > randomVal && !ans) {
ans = r;
}
});
return ans;
}
console.log(solution(resources));
Here is another implementation.
var res = [
["empty", 3],
["nitrogen", 1],
["silicon", 2],
["cobalt", 3],
["magnesium", 1]
];
var flat = [];
var item;
for(var i = 0, l = res.length; i < l; i++) {
item = Array(res[i][1]+1).join(i+",");
item = item.substr(0, item.length-1);
flat.push(item);
}
flat = flat.join(",").split(",");
function get_random_item() {
var ridx = Math.floor(Math.random() * (flat.length));
return res[flat[ridx]][0];
}
var pick;
for(var p = 0; p < 50; p++) {
pick = get_random_item();
console.log(p, pick);
}
Related
I'm struggling with creating correct data template for my chart. I want it to create template for last 12 months with actual month. For example if I want to have last 12 months i should have data template looking like this:
[{id: BAD,
data: [
{
x: "11",
y: 0
},
{
x: "12",
y: 0
},
{
x: "1",
y: 0
},
...
]},
{id: GOOD,
data: [
{
x: "11",
y: 0
},
{
x: "12",
y: 0
},
{
x: "1",
y: 0
},
...
]},
...
]
It is not that simple because I don't know what to do when month increases to 12 because then its just keeps increasing value of "x" which stands for month and thus I don't know how should I implement that.
What i tried to do was just this. I have no other clues how to get this any hints how I can get that?
const NUMBER_OF_MONTHS = 12;
const getFirstMonth = (date, numOfMonths) => {
date.setMonth(date.getMonth() - numOfMonths);
return date.getMonth();
}
const createDataTemplate = () => {
const template = [];
const firstMonth = getFirstMonth(new Date(), NUMBER_OF_MONTHS)
for (let i = 0; i < RATINGS.length; i++) {
template.push({
'id': RATINGS[i],
'data': []
})
for (let j = 1; j <= NUMBER_OF_MONTHS; j++) {
template[i].data.push({
'x': `${firstMonth + j}`,
'y': 0
})
}
}
return template;
}
I solved it like this, generating an array of the next 12 months/year. Which you can then use to loop over and add to you data
const NUMBER_OF_MONTHS = 12;
const getNextTwelveMonths = () => {
const currentMonth = new Date().getMonth();
// create array with the months until the currentMonth
const months = new Array(currentMonth).fill(null).map((_, i) => i + 1);
// add the last x months to the begin of the array
for (let i = NUMBER_OF_MONTHS; i > currentMonth; i--) {
months.unshift(i);
}
return months;
};
const createDataTemplate = () => {
const template = [];
for (let i = 0; i < RATINGS.length; i++) {
template.push({
id: RATINGS[i],
data: [],
});
const nextMonths = getNextTwelveMonths();
for (let j = 0; j < nextMonths.length; j++) {
template[i].data.push({
x: `${nextMonths[j]}`,
y: 0,
});
}
}
return template;
};
I have the code to generate a random number and it seemes to be cycling back and forth between 1 or 2.
const isDnaUnique = (_DnaList = [], _dna = []) => { let foundDna =
_DnaList.find((i) => i.join("") === _dna.join("")); return foundDna == undefined ? true : false; };
const createDna = (_races, _race) => { let randNum = [];
_races[_race].layers.forEach((layer) => {
let randElementNum = Math.floor(Math.random() * 100) + 1;
let num = 0;
layer.elements.forEach((element) => {
if (randElementNum >= 100 - element.weight) {
num = element.id;
}
});
randNum.push(num); }); return randNum; };
My issue is the random number generator keeps only returning to values instead of cycling through all of them.
{
name: "Eyes",
elements: [
{
id: 0,
name: "E1",
path: `${dir}/3-eyes/E1.png`,
weight: 25,
},
{
id: 1,
name: "E2",
path: `${dir}/3-eyes/E2.png`,
weight: 25,
},
{
id: 2,
name: "E3",
path: `${dir}/3-eyes/E3.png`,
weight: 25,
},
{
id: 3,
name: "E4",
path: `${dir}/3-eyes/E4.png`,
weight: 25,
},
],
position: { x: 0, y: 0 },
size: { width: width, height: height },
},
Your results are exactly what I would expect. Let's take a look.
Your randElementNum is going to be a number from 1 to 100. All four of your elements have weight of 25. You are running through the loop for all of the elements every time. So, if the number is less than 75 (100-25), then the if statement never fires, and num will be 0. If the number is greater than or equal to 75, then the if statement fires all four times, and you'll end up with element #3. There are no other possibilities.
The next big problem is that "forEach" is the wrong tool. I've shown you how to make it work below, but you really should be using an old-fashioned "for" loop, so you can break the loop once you find an answer.
I'm not sure what effect you were trying for, but this is certainly not what you intended. Based on the name weight, were you trying to have each element get picked 25% of the time? You can do that with something like this:
const createDna = () => {
let randElementNum = Math.floor(Math.random() * 100);
console.log( randElementNum );
let num = -1;
layer.elements.forEach((element) => {
if( num >= 0 )
return;
if (randElementNum < element.weight)
{
num = element.id;
return;
}
randElementNum -= element.weight;
});
return num;
};
I have an array that is constantly updated and accordingly it is necessary to update its grouping. Example of an array:
[
{
"price": 2419.62,
"amount": 0.0266
},
{
"price": 1927.52,
"amount": 0.0217
},
...
]
I tried different options. At the moment this option is the fastest:
const points = [
{
"price": 2419.62,
"amount": 0.0266
},
{
"price": 1927.52,
"amount": 0.0217
},
...
];
const range = 500;
const spread = 1800;
const countGroup = 250;
const sizeUnitGroup = range / countGroup;
const groups = {};
for (let i = 0; i < countGroup; i++){
groups[i] = [];
try {
points.forEach((item, id) => {
if (item.price > spread + (i*sizeUnitGroup) && item.price <= spread + (i*sizeUnitGroup + sizeUnitGroup)){
groups[i].push(item);
points.splice(id, 1);
}
if (item.price > (spread + (i*sizeUnitGroup + sizeUnitGroup))) throw BreakException;
});
} catch (e) {
}
}
But even so, this function works for too long. Any ideas how this can be optimized?
You could calculate the interval for pushing the value to the wanted slot.
var points = [
{ price: 2419.62, amount: 0.0266 },
{ price: 1927.52, amount: 0.0217 },
{ price: 1800, amount: 0.07 }, // -1 not in result
{ price: 1800.000000000001, amount: 0.07 }, // 0
{ price: 1802, amount: 0.07 }, // 0
],
range = 500,
spread = 1800,
countGroup = 250,
sizeUnitGroup = range / countGroup,
groups = {};
points.forEach((item, id) => {
var i = Math.ceil((item.price - spread- sizeUnitGroup) / sizeUnitGroup);
if (i >= 0 && i < countGroup) {
groups[i] = groups[i] || [];
groups[i].push(item);
}
});
console.log(groups);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm building irregular time graphs with HighCharts that at the moment look like so:
And I'm wondering if it's possible to create an 'average' line for the three (or possibly more in future) lines.
It would start following the blue line, then go closer to the green line mid-January, etc.
At the moment the code I'm working with looks like:
$('#chart').highcharts({
chart: { type: 'spline' },
title: { text: '' },
xAxis: { type: 'datetime' },
yAxis: {
title: { text: '' }
}
series: [{
name: 'Line 1',
data: [
[Date.UTC(2014,0,16), 173.33],
[Date.UTC(2014,0,23), 163.33],
[Date.UTC(2014,0,30), 137.67],
[Date.UTC(2014,1,6), 176.33],
[Date.UTC(2014,1,13), 178.67],
[Date.UTC(2014,1,27), 167.33],
],
color: 'purple'
},
{
name: 'Line 2',
data: [
[Date.UTC(2014,0,11), 156.33],
[Date.UTC(2014,1,15), 167.67],
],
color: 'green'
},
{
name: 'Line 3',
data: [
[Date.UTC(2014,0,1), 135],
[Date.UTC(2014,0,5), 146.33],
[Date.UTC(2014,0,27), 146.75],
],
color: 'blue'
}]
});
What you are describing is called a trend or regression line. Highcharts doesn't have a built in ability to add these lines, but the math isn't too difficult (and besides, it's more fun to do it yourself). I've coded up the simplest example I can using least squared linear regression.
/////////////////////
//utility functions//
////////////////////
// linear regression
// given array of x values and array of y values
// returns rV object with slope/intercept
lineFit = function(xs, ys, rV){
rV.slope = 0.0;
rV.intercept = 0.0;
rV.rSquared = 1.0; // assume perfection
if (xs.length < 2)
{
return false;
}
if (xs.Count != ys.Count)
{
return false;
}
var N = xs.length;
var sumX = sumFunc(xs,null);
var sumY = sumFunc(ys,null);
var funcSq = function(i){return (i*i);}
var funcSq2 = function(i,j){return (i*j);}
var sumXx = sumFunc(xs, funcSq);
var sumYy = sumFunc(ys, funcSq);
var sumXy = sumFunc(zip(xs,ys),funcSq2);
rV.slope = ((N * sumXy) - (sumX * sumY)) / (N * sumXx - (sumX*sumX));
rV.intercept = (sumY - rV.slope * sumX) / N;
rV.rSquared = Math.abs((rV.slope * (sumXy - (sumX * sumY) / N)) / (sumYy - ((sumY * sumY) / N)));
return true;
}
// sums arrays with optional function transformation
sumFunc = function(arr, func){
var total = 0;
$.each(arr, function(i,k){
if ($.isArray(k)){
if (func == null){
k = k[0] + k[1];
}else{
k = func(k[0],k[1]);
}
} else {
if (func != null){
k = func(k);
}
}
total += k;
});
return total;
}
// python style zip function
// to pair to array together
zip = function(arr1,arr2) {
var rV = [];
for(var i=0; i<arr1.length; i++){
rV.push([arr1[i],arr2[i]]);
}
return rV;
}
The lineFit function will return the rV object (by reference) with attributes of slope and intercept. After that you can add a line to Highcharts with good old fashioned y = slope * x + intercept and minX is the starting value for the regression line and maxX is the ending value.
{
name: 'Regression Line',
data: [[minX, reg.slope * minX + reg.intercept],
[maxX, reg.slope * maxX + reg.intercept]],
color: 'red',
marker:{enabled:false},
lineWidth: 5
}
Working fiddle here.
Based on ideas provided by the answer from Mark, I wrote some code to generate a custom fourth line, using the data from all three lines, and calculating the required value for each point.
My new code is as follows:
line1 = [
[Date.UTC(2014,0,16), 173.33],
[Date.UTC(2014,0,23), 163.33],
[Date.UTC(2014,0,30), 137.67],
[Date.UTC(2014,1,6), 176.33],
[Date.UTC(2014,1,13), 178.67],
[Date.UTC(2014,1,27), 167.33],
];
line2 = [
[Date.UTC(2014,0,11), 156.33],
[Date.UTC(2014,1,15), 167.67],
];
line3 = [
[Date.UTC(2014,0,1), 135],
[Date.UTC(2014,0,5), 146.33],
[Date.UTC(2014,0,27), 146.75],
[Date.UTC(2014,2,2), 168.75]
];
function average(array, index) {
sum = array[0][1];
for(var i = 1; i <= index; i++) {
sum += array[i][1];
}
value = sum / (index + 1);
return parseFloat(value.toFixed(2));
}
// Make a fourth line with all of the data points for the other
// three lines, sorted by date
all_lines = line1.concat(line2).concat(line3);
all_lines.sort(function(a, b) { return a[0] - b[0]});
// Calculate the value for each data point in the fourth line -
// the average of all the values before it
average_line = [];
for(var i = 0; i < all_lines.length; i++) {
average_line.push([all_lines[i][0], average(all_lines, i)])
}
$('#chart').highcharts({
chart: { type: 'spline' },
title: {
text: '',
},
xAxis: {
type: 'datetime'
},
yAxis: {
title: {
text: ''
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
borderWidth: 0
},
series: [{
name: 'Line 1',
data: line1,
color: 'purple'
},
{
name: 'Line 2',
data: line2,
color: 'green'
},
{
name: 'Line 3',
data: line3,
color: 'blue'
},
{
name: 'Average',
data: average_line,
color: 'red'
}]
});
The graph as it looks now (with one extra data point on the blue line) is:
I am trying to sort an array into descending order.
This is my current code:
for(var i = 0; i < scoresArray.length; i){
function swap(a, b) {
var temp = scoresArray[a].score;
scoresArray[a] = scoresArray[b].score;
scoresArray[b] = temp;
}
for(var x = 0; x < scoresArray.length; x++){
if(scoresArray[x].score < scoresArray[++x].score){
console.log(x);
swap(x, ++x);
}
}
}
return scoresArray.content;
This is the input array:
[
{ url: 'www.lboro.ac.uk', score: 6 },
{ url: 'www.xyz.ac.uk', score: 3 },
{ url: 'www', score: 8 } ]
This (should be) the output array:
[{ url: 'www.xyz.ac.uk', score: 3 },
{ url: 'www.lboro.ac.uk', score: 6 },
{ url: 'www', score: 8 } ]
Like #Douglas said, using array.sort(compareFunction) makes this easier:
var scoresArray = [
{ url: 'www.lboro.ac.uk', score: 6 },
{ url: 'www.xyz.ac.uk', score: 3 },
{ url: 'www', score: 8 } ];
scoresArray.sort(function(a,b) {
return a.score - b.score;
});
Note that, since scoresArray[i].score are numbers, you can use return a.score - b.score. In a more general case (e.g. if they were strings), you could use
scoresArray.sort(function(a,b) {
if(a.score > b.score) return 1;
if(a.score < b.score) return -1;
return 0;
});
The swap function isn't working, it replaces the values in scoresArray with just the score numbers. It is also important to know that ++x changes the value of x. I think you mean x + 1 instead.
This roughly works:
var scoresArray = [
{ url: 'www.lboro.ac.uk', score: 6 },
{ url: 'www.xyz.ac.uk', score: 3 },
{ url: 'www', score: 8 } ];
function swap(a, b) {
var temp = scoresArray[a];
scoresArray[a] = scoresArray[b];
scoresArray[b] = temp;
}
for(var i = 0; i < scoresArray.length; i++) {
for(var x = 0; x < scoresArray.length - 1; x++) {
if (scoresArray[x].score > scoresArray[x + 1].score) {
swap(x, x + 1);
}
}
}
console.log(scoresArray);
But it would be better to use array.sort:
var scoresArray = [
{ url: 'www.lboro.ac.uk', score: 6 },
{ url: 'www.xyz.ac.uk', score: 3 },
{ url: 'www', score: 8 } ];
scoresArray.sort(function(a, b) {
return b.score - a.score;
});
console.log(scoresArray);