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;
};
Related
I am having two array like this,
let array1 = [
{
"id": 23,
"name": "Telangana",
}
]
Here i need to update array2 color value inside properties based on array1 numberOfProjects value inside latestMetric. As u can see that in both arrays stateId and id are same.If numberOfProjects value is in the range 1 - 1000. I need to update the color value as 1. then numberOfProjects value is in the range 1000 - 2000. I need to update the color value as 2.so on. I dont know how to achieve this. I tried to map those two arrays and can able to get the ID's.But i dont know how to compare them and update the value . Pleas help me.Thanks in advance
You can do like this
let updatedArr2 = [];
function updateArr2(arr2values, colorValue) {
let updatedProperties = { ...arr2values.properties, color: colorValue };
arr2values.properties = updatedProperties;
updatedArr2.push(arr2values);
}
array2.map(arr2values =>
array1.map(arr1values => {
if (arr2values.properties.stateId === arr1values.latestMetric.stateId) {
if (
arr1values.latestMetric.numberOfProjects >= 1 &&
arr1values.latestMetric.numberOfProjects <= 1000
) {
updateArr2(arr2values, 1);
} else if (
arr2values.latestMetric.numberOfProjects >= 1000 &&
arr2values.latestMetric.numberOfProjects <= 2000
) {
updateArr2(arr2values, 2);
}
}
})
);
console.log(updatedArr2);
You could loop through each object in array1 and then check if there's any object in array2 that matches the stateId, if so, then check the number of projects in the array1 object and change the color of the object in array2 that has the same stateId, something like:
array1.forEach((o) => {
let matches = array2.filter(
(o2) => o2.properties.stateId === o.latestMetric.stateId
);
let projects = o.latestMetric.numberOfProjects;
for (let match of matches) {
if (projects > 1 && projects < 1000) {
match.properties.color = 1;
} else if (projects >= 1000 && projects < 2000) {
match.properties.color = 2;
}
}
});
let array1 = [
{
id: 23,
name: "Telangana",
code: "lnn",
regionId: 1,
isActive: true,
latitude: 17.8495919,
longitude: 79.1151663,
latestMetric: {
stateId: 23,
year: 0,
constructionValueInMn: 84623,
constructionAreaInMnSqft: 32,
numberOfProjects: 406,
noOfCompletedProjects: 19,
noOfOngoingProjects: 387,
noOfUpcomingProjects: 0,
growthRate: 0,
averagePricePerSqftInRs: 0,
totalAreaInMnSqft: 71,
overAllAvgSqft: 0,
eachVariantAvgSqft: 0,
noOfTypeOfVariant: 0,
projectCompletionCycle: 0,
},
createdAt: "2020-04-21T00:35:11.684134",
updatedAt: "2020-04-21T00:35:11.684134",
},
];
let array2 = [
{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
[
[77.19721, 28.861519],
[77.203836, 28.86004],
],
],
},
properties: {
cartodb_id: 26,
state_code: 7,
st_nm: "NCT of Delhi",
color: 2,
id: 23,
stateId: 23,
},
},
];
array1.forEach((o) => {
let matches = array2.filter(
(o2) => o2.properties.stateId === o.latestMetric.stateId
);
let projects = o.latestMetric.numberOfProjects;
for (let match of matches) {
if (projects > 1 && projects < 1000) {
match.properties.color = 1;
} else if (projects >= 1000 && projects < 2000) {
match.properties.color = 2;
}
}
});
console.log(array2);
Try this:
array2.map(arr2 => {
//Find to return the position when the id's are the same
const arr1 = array1.find(arr => arr.latestMetric.stateId == arr2.properties.id)
// If find was successful, do this
if (arr1) {
// Destructuring assignment to be easier to compare
const { numberOfProjects } = arr1.latestMetric
if (numberOfProjects >= 1 && numberOfProjects < 1000)
arr2.properties.color = 1
else if (numberOfProjects >= 1000 && numberOfProjects < 2000)
arr2.properties.color = 2
}
})
https://jsfiddle.net/JungEun1997/nb3o1987/50/
I have reached the desired result but you want to see it in a simpler way.
I tried using map and filter but failed.
I want to change this obj_wrap to obj_a in a simpler way!
(start_num and end_num differ by 1)
var obj_wrap = {
'time':[{
'start_num': 10,
'end_num':11
},{
'start_num': 3,
'end_num':4
},{
'start_num': 1,
'end_num':2
},{
'start_num': 2,
'end_num':3
},{
'start_num': 6,
'end_num':7
}]
}
var obj_a = {
'time':[{
'start_num': 1,
'end_num':4
},{
'start_num': 6,
'end_num':7
},{
'start_num': 10,
'end_num':11
}]
}
I used this method.
var obj_b = {'time':[]}
$.each(obj_wrap.time,function(time_key,time_val){
$.each(obj_wrap.time,function(chk_key,chk_val){
if(time_val.start_num === chk_val.end_num){
obj_wrap.time[time_key]['start_num'] = chk_val.start_num
obj_wrap.time[chk_key] = ""
}
if(time_val.end_num === chk_val.start_num){
obj_wrap.time[time_key]['end_num'] = chk_val.end_num
obj_wrap.time[chk_key] = ""
}
});
})
$.each(obj_wrap.time,function(key,value){
if(value!==""){
obj_b.time.push(value)
}
})
obj_b.time.sort(function (a, b) {
return a.start_num < b.start_num ? -1 : a.start_num > b.start_num ? 1 : 0;
});
You can use Bucket Sort algorithm in this case:
var obj_wrap = {
'time':[{
'start_num': 10,
'end_num':11
},{
'start_num': 3,
'end_num':4
},{
'start_num': 1,
'end_num':2
},{
'start_num': 2,
'end_num':3
},{
'start_num': 6,
'end_num':7
}]
};
var time = obj_wrap.time;
var bucket = [];
time.forEach(({start_num, end_num}) => {
for (var i = start_num; i < end_num; i++) {
bucket[i] = true;
}
});
var newTime = [];
bucket.forEach((_, index) => {
if (bucket[index - 1]) {
newTime[newTime.length - 1].end_num = index + 1;
} else {
newTime.push({start_num: index, end_num: index + 1});
}
});
var obj_a = {time: newTime};
console.log(obj_a);
The complexity is O(n).
I've implemented just a simple memoized cache of your data with the Key Value pair representing start_num & end_num values (See code comments for visual).
Once the memo has been created, you can iterate across the memo in linear time, and populate a results array accordingly. For that part, i opted for reduce, since it persists a transient state that's accessible for each iteration.
On each iteration, i basically just check to see if I should continue iterating without populating the times array. Once i've detected a break in the number chain, there's some edge case checks conducted before populating the times array with the expected results.
Time & Space Complexity is O(n).
const objWrap = {
time:[{
start_num: 10,
end_num:11
}, {
start_num: 3,
end_num:4
}, {
start_num: 1,
end_num:2
}, {
start_num: 2,
end_num:3
}, {
start_num: 6,
end_num:7
}],
};
const memo = objWrap.time.reduce((acc, next) => {
if (!Reflect.has(acc, next.start_num)) {
acc[next.start_num] = next.end_num;
}
return acc;
}, {});
/*
memo is now:
{
1: 2,
2: 3,
3: 4,
6: 7,
10: 11
}
NOTE: If you store key's as numbers in a JS object, they'll be automatically sorted.
*/
const entries = Object.entries(memo);
const result = entries
.slice(1) // iterate across all entries except the first since we'll use the first entry to initialize our accumulator.
.reduce((acc, [start,end], i, arr) => {
if (Reflect.has(memo, acc.next)) { // if we found a sequence, then just continue iterating.
acc.next = end;
} else {
acc.times.push({ // if the sequence is broken, then we have a result.
start_num: Number(acc.start), // cast back to number, as it's currently a string
end_num: acc.next,
});
if (i === arr.length - 1) { // if we've reached the end of the array, then prepare the last result as well.
acc.times.push({
start_num: Number(start),
start_end: end,
});
delete acc.next;
delete acc.start;
} else { // if we haven't reached the end of the array, then prepare the next iteration's comparison.
acc.start = start;
acc.next = end;
}
}
return acc;
}, {
next: entries[0][1], // initialize accumulator with first entryies end value.
start: entries[0][0], // initialize accumulator with first entryies start value.
times: [],
});
console.log(JSON.stringify(result, null, 2))
I currently trying to make a lucky dip selector for prizes, I have an object that I would like to somehow transform into percentages which all depend on one another. I looked at other solutions online and have trouble doing so where I am looking inside an array and returning the whole object inside by chance
The max percentage in example will be 100%, and each qty would work out to the correct percentage that qty: 22 has the most likely chance of being picked and qty: 1 being the rarest and so on
{ tshirts: [
{ "tshirtName":"Green Shirt", "qty": 1, },
{ "tshirtName":"Blue Shirt", "qty": 2, },
{ "tshirtName":"Red Shirt", "qty": 7, },
{ "tshirtName":"Yellow Shirt", "qty": 22, },
]}
At the moment I made the mistake of just doing the following:
const randomChoice = Math.floor(Math.random() * prizes.tshirts.length);
console.log('Your prize is ' + prizes.tshirts[randomChoice].tshirtName);
But this does not take into account the different percentages needed for each qty, it makes it all a 25% chance each
Any help would be very appreciated, thank you
Find a random number between 1 and total quantity sum and get an element based on that.
let prizes = {"tshirts":[{"tshirtName":"Green Shirt","qty":1},{"tshirtName":"Blue Shirt","qty":2},{"tshirtName":"Red Shirt","qty":7},{"tshirtName":"Yellow Shirt","qty":22}]};
// variable for total quantity sum
let total = 0;
// create an array to keep reference of maximum value of random num
let range = prizes.tshirts.reduce((arr, { qty }, i) => {
arr[i] = (total += qty);
return arr;
}, [])
for (let i = 0; i < 100; i++) {
// get a random number between 1 to total
const randomChoice = Math.floor(Math.random() * total) + 1;
// get index based on the range array values
const index = range.findIndex(v => v >= randomChoice)
console.log('Your prize is ' + prizes.tshirts[index].tshirtName, randomChoice);
}
You can use an array and store a particular tshirts name as often as it's quantity.
Let me illustrate by a simple example. Imagine you have an array of numbers that looks a little like this:
var arr=[1,1,1,2];
If you now pick a random element out of this array using:
var element=arr[Math.floor(Math.random() * arr.length)];
there will be a high chance that the chosen element will be a 1 simply because there are 3 times as much 1s than 2s.
The same principle can be applied to your use case though we use the shirt names instead of numbers.
var prizes = {
tshirts: [{
"tshirtName": "Green Shirt",
"qty": 1,
},
{
"tshirtName": "Blue Shirt",
"qty": 2,
},
{
"tshirtName": "Red Shirt",
"qty": 7,
},
{
"tshirtName": "Yellow Shirt",
"qty": 22,
},
]
};
var tempArray = [];
prizes.tshirts.forEach(shirt => {
for (var a = 0; a < shirt.qty; a++) {
tempArray.push(shirt.tshirtName)
}
});
var element = tempArray[Math.floor(Math.random() * tempArray.length)];
console.log(element);
How about this?
let prizes = {
tshirts: [
{'tshirtName': 'Green Shirt', 'qty': 1},
{'tshirtName': 'Blue Shirt', 'qty': 2},
{'tshirtName': 'Red Shirt', 'qty': 7},
{'tshirtName': 'Yellow Shirt', 'qty': 22}
]
};
// total count of shirts
let count = prizes.tshirts.reduce((a, b) => a + b.qty, 0);
// Create an array containing each shirt instance qty times
let extendedArray = prizes.tshirts
.map(shirt => new Array(shirt.qty).fill(shirt)) // [[shirt1, shirt1], [shirt2, shitr2]]
.flat(); // [shirt1, shirt1, shirt2, shirt2]
console.log(getRandomShirt());
/**
* Simple integer random function
*/
function random(max, min) {
return Math.round(Math.random() * max) + min;
}
/**
* Call this for getting a shirt.
* Note, that the returned shirt is still the same instance like in prizes.tshirts
*/
function getRandomShirt() {
let randomValue = random(count, 0);
return extendedArray[randomValue];
}
const prizes = { tshirts: [
{ "tshirtName":"Green Shirt", "qty": 1,},
{ "tshirtName":"Blue Shirt", "qty": 2, },
{ "tshirtName":"Red Shirt", "qty": 7, },
{ "tshirtName":"Yellow Shirt", "qty": 22, }
]};
let Data = [];
prizes.tshirts.map(tshirt => {
let i = 0;
while( i < tshirt.qty){
Data.push(tshirt.tshirtName); i++; };
});
let randomChoice = Math.floor(Math.random()*Data.length);
console.log(Data[randomChoice]);
I have the following array:
var data = [{
length: 900,
fields: 3
},{
length: 1150,
fields: 4
},{
length: 1700,
fields: 5
}];
Now I would like to have a function that returns the fields depending on the given length like:
function getFields(length) {
// return "3" if length <= 900
// return "4" if length <= 1150
// return "5" if length <= 1700
}
How could I achieve this?
As long as data is properly sorted, it is a simple for loop
var data = [{
length: 900,
fields: 3
},{
length: 1150,
fields: 4
},{
length: 1700,
fields: 5
}];
function getFields (value) {
var i;
for (i=0; i<data.length; i++) {
if (value <= data[i].length) return data[i].fields; // exit since we found first match
}
return 0; // what ever the default is for no match
}
console.log(800, getFields(800));
console.log(900, getFields(900));
console.log(1000, getFields(1000));
console.log(1500, getFields(1500));
console.log(2000, getFields(2000));
or with modern array methods you can use find() which is like a for loop code above under the hood:
var data = [{
length: 900,
fields: 3
},{
length: 1150,
fields: 4
},{
length: 1700,
fields: 5
}];
function getFields (value) {
var i;
var match = data.find(function(item) {
return value <= item.length
})
return match ? match.fields : 0;
}
console.log(800, getFields(800));
console.log(900, getFields(900));
console.log(1000, getFields(1000));
console.log(1500, getFields(1500));
console.log(2000, getFields(2000));
Now if the data array is out of order, than it should be sorted.
I'd define it like so:
function getFields(length) {
var d = data
.filter(d => d.length <= length) // get the list of matching objects
.sort((a, b) => b.length - a.length) // sort descending so largest value is at the front of the array
.shift(); // get the first element from the array
return (d !== undefined) ? d.fields : undefined;// if that element exists, return .fields, otherwise undefined
}
In action:
var data = [{
length: 900,
fields: 3
},{
length: 1150,
fields: 4
},{
length: 1700,
fields: 5
}];
function getFields(length) {
var d = data
.filter(d => d.length <= length) // get the list of matching objects
.sort((a, b) => b.length - a.length) // sort descending so largest value is at the front of the array
.shift(); // get the first element from the array
return (d !== undefined) ? d.fields : undefined;// if that element exists, return .fields, otherwise undefined
}
var tests = [1700, 1150, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700];
console.log(tests.map(getFields));
While I don't know if this is performant enough for your current use case, but it's relatively readable and easy-to-follow (although this could be made more efficient if the data were always ordered by length, for instance). If you need something more performant, you could do something like this instead:
function getFields(length) {
let d;
let i = data.length - 1;
while (i > -1 && d === undefined) {
if (data[i].length <= length) {
d = data[i].fields;
}
i -= 1;
}
return d;
}
In action:
var data = [{
length: 900,
fields: 3
},{
length: 1150,
fields: 4
},{
length: 1700,
fields: 5
}];
function getFields(length) {
let d;
let i = data.length - 1;
while (i > -1 && d === undefined) {
if (data[i].length <= length) {
d = data[i].fields;
}
i -= 1;
}
return d;
}
var tests = [1700, 1150, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700];
console.log(tests.map(getFields));
you can iterate the data and match the conditon
var data = [{
length: 900,
fields: 3
},{
length: 1150,
fields: 4
},{
length: 1700,
fields: 5
}];
function getFields(len) {
var fields = '';
$.each(data, function(key,value) {
if(value.length<=len)
fields = value.fields;
});
return fields;
}
// call function
alert(getFields(1700));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You don't need jQuery for this, it can be done with e.g. a standard .find() call. Note that this assumes that the data is sorted by .length as in your example.
var data = [{
length: 900,
fields: 3
}, {
length: 1150,
fields: 4
}, {
length: 1700,
fields: 5
}];
var value = 950;
var matching = data.find(x => value <= x.length);
var fields = matching ? matching.fields : 0;
console.log(fields);
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);
}