Object.values() not updating variable - javascript

I have the variable G.playerStatsDifference defined as an array of objects:
playerStatsDifference: [{
carpenter: 0,
wood: 0,
gunman: 0,
gunpowder: 0,
merchant: 0,
gold: 0,
fleet: 0,
flagship: 0,
}, {
carpenter: 0,
wood: 0,
gunman: 0,
gunpowder: 0,
merchant: 0,
gold: 0,
fleet: 0,
flagship: 0,
}]
The point of this variable is to calculate the difference between G.playerStats which frequently changes.
My function to calculate the difference is:
const oldPlayerStats = JSON.parse(JSON.stringify(G.playerStats));
statsDifference(G, oldPlayerStats);
for (let p = 0; p < 2; p++) {
for (let s = 0; s < 8; s++) {
Object.values(G.playerStatsDifference[p])[s] = Object.values(G.playerStats[p])[s] - Object.values(oldPlayerStats[p])[s];
}
}
The expected output would be to have playerStatsDifference
When running some tests I did some console logging and it gave me the correct calculations, but the G.playerStatsDiffence would not update.
Here is some of that testing, with the calulations being correct:
console.log("Current wood is " + Object.values(G.playerStats[0])[1]); //Current wood is 5
console.log("Old wood is " + Object.values(oldPlayerStats[0])[1]); //Old wood is 10
console.log(Object.values(G.playerStats[0])[1] - Object.values(oldPlayerStats[0])[1]); //-5
I thought maybe I was doing something wrong with the loops so I tried the following afterwards:
Object.values(G.playerStatsDifference[0])[1] = Object.values(G.playerStats[0])[1] - Object.values(oldPlayerStats[0])[1];
However this did not work either. Having said that, the following does work:
G.playerStatsDifference[0].wood = Object.values(G.playerStats[0])[1] - Object.values(oldPlayerStats[0])[1];
So it seems like I have some issue with the Object.values on G.playerStatsDifference. Any idea on why that is and how I can run that through the loop?
=====
EDIT: As those in the comments have pointed out my question is a bit confusing so I will try to clear it up here..
The G.playerStatsDifference value is supposed to track the difference between the previous value of G.playerStats and the current value of G.playerStats.
To do this I am setting the value of oldPlayerStats to equal G.playerStats and then updating G.playerStats to its new value.
I then need to run through the array of objects and subtract the value of G.playerStats from oldPlayerStats. This will produce the value of G.playerStatsDifference
That is what the loop is for, to go through each object key and do the calculation.
Hope this provides some clarity. Sorry for the poorly worded question.

const diffBetweenObjectValues = (a, b) => {
return Object.entries(a).reduce((result, [aKey, aVal]) => {
result[aKey] = aVal - (b[aKey] ?? 0);
return result;
}, {});
}
const stats = { a: 1, b: 2 };
const updatedStats = { a: 1, b: 1 };
// Initial player stats are { a: 1, b: 2 }
const player = { stats: stats, diff: {} };
// Set the diff, value is { a: 0, b: 1 }
player.diff = diffBetweenObjectValues(player.stats, updatedStats);
// Actually update the stats, value is { a: 1, b: 1 }
player.stats = updatedStats;
Note that if a key is present in b but not a it's ignored. Also note that this only works properly if all the property values are numeric.
You can put the state transition in a function and just run it when you need to update the stats (like every tick of the game loop).
Response to comment
Ok, lets add another helper function
const zip = (a, b) => a.map((x, i) => [x, b[i]]);
const players = [...]; // array of players
const statUpdates = [...]; // array of stat updates
zip(players, statUpdates).forEach(([player, stats]) => {
player.diff = diffBetweenObjectValues(player.stats, stats);
player.stats = stats;
});
Zip combines the array of players and the array of stat updates in to pairs, then iterate over them with forEach, destructure the bits back out, and run the update. You can also just use a for loop, which is faster but harder to read and easier to get wrong (e.g. off-by-one errors). I would stick with the version until/unless your profiler tells you it's too slow.
Update 2
const currentStats = [{ a: 1, b: 2 }, {a: 3, b: 2 }];
const updatedStats = [{ a: 0, b: 1 }, {a: 4, b: 1 }];
const diffedStats = zip(currentStats, updatedStats).map(([current, updated]) => {
return diffBetweenObjectValues(current, updated);
});

// for testing purposes, create an object with some random stats
const randomPlayerStats = () => Object.fromEntries(
['carpenter','wood','gunman','gunpowder','merchant','gold','fleet','flagship']
.map(k=>[k,Math.random()*10|0]));
// array of the last player stats recorded for each player
let lastPlayerStats = [];
// create a new object from the existing object, subtracting each entry
// from the old object from the entry from the new object
// note: uses the ?? operator so that if there is no last object yet,
// the last object value will be treated as being zero
const difference = (playerStats, lastPlayerStats) => {
let r = Object.fromEntries(Object.entries(playerStats).map(([k,v])=>
[k, v-(lastPlayerStats?.[k]??0)]));
lastPlayerStats = playerStats;
return r;
};
// simulate 5 rounds of the game, with 2 players in the game
const playerCount = 2;
const simulatedRounds = 5;
for(let c=0;c<simulatedRounds;c++) {
let playerStats = [...Array(playerCount).keys()].map(i=>randomPlayerStats());
let playerStatsDifference = playerStats.map((s,i)=>
difference(s, lastPlayerStats[i]??{}));
console.log('playerStats:');
console.log(playerStats);
console.log('playerStatsDifference:');
console.log(playerStatsDifference);
}

Related

Chance of something to happen using integers as percentages

I'm working on a job system where you can mine different types of ores in a discord bot. Depending on your skill, the chances (stored in a database) to receive certain ores change. Some will have a 0% chance at certain skill levels. At Skill 1 the chances will be the following:
let coal_chance = 80
let copper_chance = 15
let iron_chance = 5
let gold_chance = 0
let diamond_chance = 0
let emerald_chance = 0
And at Skill 2 the following:
let coal_chance = 50
let copper_chance = 35
let iron_chance = 10
let gold_chance = 5
let diamond_chance = 0
let emerald_chance = 0
And so on. My question is, how can I make a chance system based on these percentages?
I tried making a system using Math.Random() and an if statement, but since I have to check for a different amount of values each time I would have to make one for every skill level, and if I ever wanted to set the chance of a certain ore to 0% inside the database, I would also have to change the code.
The crude solution I came up with based on the comment of #sudheeshix:
const cumulativeChances = [
{c: 100, i: "coal"},
{c: 50, i: "copper"},
{c: 15, i: "iron"},
{c: 5, i: "gold"},
{c: 0, i: "emerald"},
{c: 0, i: "diamond"}
]
const mineOre = () => {
let rng = Math.random() * 100
let won = 'coal'
for (item of cumulativeChances) {
if (item.c < rng) break
won = item.i
}
return won
}
// Testing if it works as intended
const calculatedChances = {
coal: 0,
copper: 0,
iron: 0,
gold: 0,
emerald: 0,
diamond: 0
}
let tries = i = 10000;
while(i--) {
let won = mineOre()
calculatedChances[won] += (1 / tries * 100)
}
console.log(calculatedChances)
There is probably room for improvement, but it provides an idea. Happy coding :)
Here's one way:
Convert your resource values to an array:
resources = ["coal", "copper", "iron", "gold", "diamond", "emerald"];
chances = [80, 15, 5, 0, 0, 0];
Then, generate cumulative sums of the percentages, based on this answer.
const cumulativeSum = (sum => value => sum += value/100)(0);
cum_chances = chances.map(cumulativeSum);
The values are divided by 100 to get the float values as that's what Math.random() generates: values between 0 and 1.
With the values in chances mentioned above, this will make cum_chances with values [0.8, 0.95, 1, 1, 1, 1].
Now, generate the random probability.
random_prob = Math.random();
And find the index of the first array item that where random_prob falls in, based on this answer.
idx = cum_chances.findIndex(function (el) {
return el >= random_prob;
});
resource_found = resources[idx];
For example, if random_prob is 0.93, this will give you an index of 1 and so, resources[idx] will give you "copper".

How to iterate over an object to find out 'periods of time' in which the value is larger than 0?

I am building a booking app. I have created an object with times and the number of vacancies for each of these times.
{
1000: 1,
1030: 4,
1100: 4,
1130: 2,
1200: 0,
1230: 1,
1300: 0
//...
}
Times are separated in 30 minute intervals, but services can take longer than 30 minutes (but are all multiples of 30 themselves). E.g.: service1.duration = 90
I now need to build a script that identifies in which periods of time a service can be executed. In the example above, 90/30 = 3, so I would have to find 3 sequential keys in that object that have a value > 0.
There would be two in the example above: [1000, 1030, 1100] and [1030, 1100, 1130].
Ideally, the periods would be returned in an array just as the two I have exemplified.
Problem: I don't know how to iterate over both keys and values. I know Object.keys and Object.values can be used, but not how to combine them.
You can use both Object.keys and Object.values together, as they are both guaranteed to be in ascending order for number-like keys.
const times = {
1000: 1,
1030: 4,
1100: 4,
1130: 2,
1200: 0,
1230: 1,
1300: 0
//...
};
const getPeriods = time => {
const keys = Object.keys(times);
const values = Object.values(times);
const res = [];
for(let i = 0; i <= values.length - time; i++){
let works = true;
for(let j = i; j < i + time && works; j++){
if(values[j] <= 0){
works = false;
}
}
if(works){
res.push(keys.slice(i, i + time));
}
}
return res;
};
console.log(getPeriods(3));

Return array index where sub value is max with underscore.js

I have a setup like this:
docs[0]['edits'] = 1;
docs[1]['edits'] = 2;
I want to get the docs[index] of the one with the most edits.
Using Underscore I can get the appropriate array (ie the value docs[1]), but I still don't know the actual index in relation to docs.
_.max(docs, function(doc) { return doc['edits']; });
Any help would be greatly appreciated.
To do it without a library, iterate over the array (possibly with reduce), storing the highest number so far and the highest index so far in a variable, reassigning both when the item being iterated over is higher:
const edits = [
3,
4,
5,
0,
0
];
let highestNum = edits[0];
const highestIndex = edits.reduce((highestIndexSoFar, num, i) => {
if (num > highestNum) {
highestNum = num;
return i;
}
return highestIndexSoFar;
}, 0);
console.log(highestIndex);
Another way, with findIndex, and spreading the edits into Math.max (less code, but requires iterating twice):
const edits = [
3,
4,
5,
0,
0
];
const highest = Math.max(...edits);
const highestIndex = edits.indexOf(highest);
console.log(highestIndex);
Just use maxBy
const _ = require('lodash');
const docs = [{'edits': 1}, {'edits': 2}, {'edits': 0}, {'edits': 4}, {'edits': 3}]
const result = _.maxBy(docs, a => a.edits)
console.log(result)
https://repl.it/#NickMasters/DigitalUtterTechnologies
Pure JS way
const result2 = docs.reduce((result, { edits }) => edits > result ? edits : result, Number.MIN_SAFE_INTEGER)
console.log(result2)

initializing in loop array with objects

I want to create such array in loop
dataset: [
{
x: 0,
y: 0,
},
{
x: 1,
y: 0.993,
}
]
But this way is not correct.
var array = new Array(10);
for (var i = 0; i < 5; ++i) {
array[i].x = 1;
array[i].y = 2;
}
How I can initialize in correct way?
The comments made by SLaks and squint are correct, so this answer is more of an explanation of why your code isn't working like you think it should, and an example of what you could do instead.
You created an array with room to hold 10 things but you didn't specify what those things were and so nothing is contained in the array.
var array = new Array(10);
you can visualize your array like this:
array = [undefined, undefined, undefined, undefined,...
The array you created was just a container for 10 things not yet defined. When you tried to assign the 'x' and 'y' properties of the array elements, you were were trying to operate on something that did not exist. To get what you want, I suggest creating an object that has the properties you want, with initial values, and then use your loop to add the number of elements you want.
var array = [];
var arrayObject = {x:0,y:0};
for(i=0; i < 10; i++){
array.push(arrayObject);
}
You can do this job in one assignment line as follows;
var dataSet = (new Array(10)).fill("initial y value").reduce((p,c,i) => p.concat({x:i,y:c}),[]);
console.log(dataSet);
I just couldn't figure what y values you would like to have so inserted the initial values of the array. Change them the way you like later. I hope it helps.
Replace the new Array(10) with
var array = Array.apply( {}, { length: 10 } ).map( function() { return {} });
new Array(10) is creating an array like
[ undefined, undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined]
So you are trying to assign x on undefined
If you tried
new Array(10).map(function(){ return {}; }) it will not work either.
An es6 way to do it would be
Array.from(new Array(10), () => { return { x: 1, y: 2 }; })
In JavaScript the Array acts different than in static-typed languages, so there's no need to initialize it with fixed length.
For ECMAScript 6 specification and later:
var points = [].fill.call({ length: 5 }, {x: 1, y: 1});
It produces
[{x: 1, y: 1},
{x: 1, y: 1},
{x: 1, y: 1},
{x: 1, y: 1},
{x: 1, y: 1}]
To ensure old browsers' support use for loop:
var points = [{x: 1, y: 1}];
for (var i = 0; i < 5; i++) points.push(points[0]);

Fast way to get the min/max values among properties of object

I have an object in javascript like this:
{ "a":4, "b":0.5 , "c":0.35, "d":5 }
Is there a fast way to get the minimum and maximum value among the properties without having to loop through them all? because the object I have is huge and I need to get the min/max value every two seconds. (The values of the object keeps changing).
Update: Modern version (ES6+)
let obj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
let arr = Object.values(obj);
let min = Math.min(...arr);
let max = Math.max(...arr);
console.log( `Min value: ${min}, max value: ${max}` );
Original Answer:
Try this:
let obj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
var arr = Object.keys( obj ).map(function ( key ) { return obj[key]; });
and then:
var min = Math.min.apply( null, arr );
var max = Math.max.apply( null, arr );
Live demo: http://jsfiddle.net/7GCu7/1/
There's no way to find the maximum / minimum in the general case without looping through all the n elements (if you go from, 1 to n-1, how do you know whether the element n isn't larger (or smaller) than the current max/min)?
You mentioned that the values change every couple of seconds. If you know exactly which values change, you can start with your previous max/min values, and only compare with the new ones, but even in this case, if one of the values which were modified was your old max/min, you may need to loop through them again.
Another alternative - again, only if the number of values which change are small - would be to store the values in a structure such as a tree or a heap, and as the new values arrive you'd insert (or update) them appropriately. But whether you can do that is not clear based on your question.
If you want to get the maximum / minimum element of a given list while looping through all elements, then you can use something like the snippet below, but you will not be able to do that without going through all of them
var list = { "a":4, "b":0.5 , "c":0.35, "d":5 };
var keys = Object.keys(list);
var min = list[keys[0]]; // ignoring case of empty list for conciseness
var max = list[keys[0]];
var i;
for (i = 1; i < keys.length; i++) {
var value = list[keys[i]];
if (value < min) min = value;
if (value > max) max = value;
}
You could try:
const obj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
const max = Math.max.apply(null, Object.values(obj));
console.log(max) // 5
min and max have to loop through the input array anyway - how else would they find the biggest or smallest element?
So just a quick for..in loop will work just fine.
var min = Infinity, max = -Infinity, x;
for( x in input) {
if( input[x] < min) min = input[x];
if( input[x] > max) max = input[x];
}
// 1. iterate through object values and get them
// 2. sort that array of values ascending or descending and take first,
// which is min or max accordingly
let obj = { 'a': 4, 'b': 0.5, 'c': 0.35, 'd': 5 }
let min = Object.values(obj).sort((prev, next) => prev - next)[0] // 0.35
let max = Object.values(obj).sort((prev, next) => next - prev)[0] // 5
You can also try with Object.values
const points = { Neel: 100, Veer: 89, Shubham: 78, Vikash: 67 };
const vals = Object.values(points);
const max = Math.max(...vals);
const min = Math.min(...vals);
console.log(max);
console.log(min);
// Sorted
let Sorted = Object.entries({ "a":4, "b":0.5 , "c":0.35, "d":5 }).sort((prev, next) => prev[1] - next[1])
>> [ [ 'c', 0.35 ], [ 'b', 0.5 ], [ 'a', 4 ], [ 'd', 5 ] ]
//Min:
Sorted.shift()
>> [ 'c', 0.35 ]
// Max:
Sorted.pop()
>> [ 'd', 5 ]
You can use a reduce() function.
Example:
let obj = { "a": 4, "b": 0.5, "c": 0.35, "d": 5 }
let max = Object.entries(obj).reduce((max, entry) => entry[1] >= max[1] ? entry : max, [0, -Infinity])
let min = Object.entries(obj).reduce((min, entry) => entry[1] <= min[1] ? entry : min, [0, +Infinity])
console.log(max) // ["d", 5]
console.log(min) // ["c", 0.35]
Here's a solution that allows you to return the key as well and only does one loop. It sorts the Object's entries (by val) and then returns the first and last one.
Additionally, it returns the sorted Object which can replace the existing Object so that future sorts will be faster because it will already be semi-sorted = better than O(n). It's important to note that Objects retain their order in ES6.
const maxMinVal = (obj) => {
const sortedEntriesByVal = Object.entries(obj).sort(([, v1], [, v2]) => v1 - v2);
return {
min: sortedEntriesByVal[0],
max: sortedEntriesByVal[sortedEntriesByVal.length - 1],
sortedObjByVal: sortedEntriesByVal.reduce((r, [k, v]) => ({ ...r, [k]: v }), {}),
};
};
const obj = {
a: 4, b: 0.5, c: 0.35, d: 5
};
console.log(maxMinVal(obj));
To get the keys for max and min
var list = { "a":4, "b":0.5 , "c":0.35, "d":5 };
var keys = Object.keys(list);
var min = keys[0]; // ignoring case of empty list for conciseness
var max = keys[0];
var i;
for (i = 1; i < keys.length; i++) {
var value = keys[i];
if (list[value] < list[min]) min = value;
if (list[value] > list[max]) max = value;
}
console.log(min, '-----', max)
For nested structures of different depth, i.e. {node: {leaf: 4}, leaf: 1}, this will work (using lodash or underscore):
function getMaxValue(d){
if(typeof d === "number") {
return d;
} else if(typeof d === "object") {
return _.max(_.map(_.keys(d), function(key) {
return getMaxValue(d[key]);
}));
} else {
return false;
}
}
Using the lodash library you can write shorter
_({ "a":4, "b":0.5 , "c":0.35, "d":5 }).values().max();
var newObj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
var maxValue = Math.max(...Object.values(newObj))
var minValue = Math.min(...Object.values(newObj))
obj.prototype.getMaxinObjArr = function (arr,propName) {
var _arr = arr.map(obj => obj[propName]);
return Math.max(..._arr);
}
This works for me:
var object = { a: 4, b: 0.5 , c: 0.35, d: 5 };
// Take all value from the object into list
var valueList = $.map(object,function(v){
return v;
});
var max = valueList.reduce(function(a, b) { return Math.max(a, b); });
var min = valueList.reduce(function(a, b) { return Math.min(a, b); });
If we are sorting date time value then follow the below described procedure
const Obj = {
"TRADE::Trade1": {
"dateTime": "2022-11-27T20:17:05.980Z",
},
"TRADE::Trade2": {
"dateTime": "2022-11-27T20:36:10.659Z",
},
"TRADE::Trade3": {
"dateTime": "2022-11-27T20:28:10.659Z",
}
}
const result = Object.entries(Obj).sort((prev, next) => new Date(prev[1].dateTime) - new Date(next[1].dateTime))
console.log(result)

Categories

Resources