Related
I'm making a simple RPG and trying to calculate which attribute should be increased when a character levels up. They have a potential limit for each attribute and I want to increment the attribute that is furthest from its potential.
I can loop through each attribute and subtract its current value from its potential value to get the difference. I can then push the difference to an array. The result looks like:
[
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
]
Charisma is the key with the highest difference, so how can I evaluate this and return the name of the key (not the value itself)?
EDIT: Here is the logic which is used to get the array:
let difference = [];
let key;
for (key in currentAttributes) {
difference.push({[key]: potentialAttributes[key] - currentAttributes[key]});
};
Simple reduce with Object.entries
const items = [
{ Strength: 5 },
{ Dexterity: 6 },
{ Constitution: 3 },
{ Wisdom: 4 },
{ Charisma: 8 }
]
const biggest = items.reduce((biggest, current, ind) => {
const parts = Object.entries(current)[0] //RETURNS [KEY, VALUE]
return (!ind || parts[1] > biggest[1]) ? parts : biggest // IF FIRST OR BIGGER
}, null)
console.log(biggest[0]) // 0 = KEY, 1 = BIGGEST VALUE
Your data model is a bit weird with the array with objects, a better model would just be an object.
const items = {
Strength: 5,
Dexterity: 6,
Constitution: 3,
Wisdom: 4,
Charisma: 8
}
const biggest = Object.entries(items)
.reduce((biggest, current, ind) => {
const parts = current
return (!ind || parts[1] > biggest[1]) ? parts : biggest
}, null)
console.log(biggest[0])
You could create an object, take the entries and reduce the entries by taking the entry with the greatest value. At the end take the key from the entry.
var data = [{ Strength: 5 }, { Dexterity: 6 }, { Constitution: 3 }, { Wisdom: 4 }, { Charisma: 8 }],
greatest = Object
.entries(Object.assign({}, ...data))
.reduce((a, b) => a[1] > b[1] ? a : b)
[0];
console.log(greatest);
Sort in descending order and grab the first item:
let attributes = [
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
];
//for convenience
const getValue = obj => Object.values(obj)[0];
//sort descending
attributes.sort((a, b) => getValue(b) - getValue(a));
let highest = attributes[0];
console.log(Object.keys(highest)[0]);
Alternatively, go though the array and find the highest score:
let attributes = [
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
];
//for convenience
const getValue = obj => Object.values(obj)[0];
//find the highest score
let highest = attributes.reduce((currentHighest, nextItem) => getValue(currentHighest) > getValue(nextItem) ? currentHighest : nextItem);
console.log(Object.keys(highest)[0]);
I'm getting info from an api, and what i wanna do is to multiply two different values from that response, and then sum the totals. I know how to sum all the values with reduce:
function getHistoricSales(){
$http.get('api/SomeApi')
.then(function(data){
$scope.salesResult = data.data.Response;
var hResults = $scope.salesResult.reduce((a, b) => a + b.Cost, 0);
$scope.historic = hResult.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
});
}
But, if per example, on that response not only get the Cost (b.Cost), but the Quantity too. So, how can i in first place multiply every Costby it's own Quantity and then sum the results?
I'm using Javascript and AngularJs.
Hope you can help me. Thanx in advance...
I think what you're looking for is:
var hResults = $scope.salesResult
.map(sr => sr.Cost * sr.Quantity)
.reduce((a, b) => a + b);
So you want to transform each sales result (with map) into its own result (i.e. cost * quantity) and then sum those (with reduce).
For example:
let data = [
{Cost: 10, Quantity: 15},
{Cost: 5, Quantity: 11},
{Cost: 2, Quantity: 110},
{Cost: 5, Quantity: 90},
]
let result = data
.map(sr => sr.Cost * sr.Quantity)
.reduce((a, b) => a + b);
console.log(result);
If each item in $scope.salesResult optionally has a quantity property, you'll need to make sure you're not multiplying by an undefined value. Thus, your reduce() should take this condition into account.
var salesResult = [
// Some items only have the cost
{ cost: 10 },
{ cost: 10 },
// Some items might also have a quantity
{ cost: 10, quantity: 10 },
{ cost: 10, quantity: 10 },
];
var hResults = salesResult.reduce((total, result) =>
total + (result.quantity
? result.cost * result.quantity
: result.cost), 0);
console.log(hResults); // 220
I have an array of objects that represents a set of tours but there are different prices for the same tour like this:
let tours = [{'id':1, price:200},
{'id':1, price:300},
{'id':3, price:150},
{'id':2, price:110},
{'id':3, price:120},
{'id':2, price:100}]
So, I would like to pick the lowest price available by the tour ID and push it into a new array as unique tour with the lowest price.
So the result would be:
result = [{'id':1, price:200},
{'id':3, price:120},
{'id':2, price:100},]
I tried methods in Lodash like _.minBy()but it returns one from all the array.
Lodash Solution
You can _.groupBy() ids, than _.map() the result, and take the lowest of each group with _.minBy():
const tours = [{"id":1,"price":200, prop: 'prop1' },{"id":1,"price":300, prop: 'prop1'},{"id":3,"price":150},{"id":2,"price":110},{"id":3,"price":120},{"id":2,"price":100}];
const result = _(tours)
.groupBy('id')
.map((group) => _.minBy(group, 'price'))
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
VanillaJS Solution
Reduce the tours to a Map with the ids as key. On each iteration take the group with lowest price. When done, spread the Map.values() back to an array:
const tours = [{"id":1,"price":200, prop: 'prop1' },{"id":1,"price":300, prop: 'prop1'},{"id":3,"price":150},{"id":2,"price":110},{"id":3,"price":120},{"id":2,"price":100}];
const lowest = [...tours.reduce((r, o) => {
const { id, price } = o;
const current = r.get(id);
if(!current || price < current.price) r.set(id, { ...o });
return r;
}, new Map()).values()];
console.log(lowest);
Or you can use simply reduce and keep updating minimum value in the accumulator
tours.reduce( (acc, c) => {
acc[c.id] = acc[c.id] ? Math.min( c.price, acc[c.id] ) : c.price; //update min value in accumulator
return acc; // return accumulator
} ,{}) //initialize accumulator to {}
Demo
let tours = [{
'id': 1,
price: 200
},
{
'id': 1,
price: 300
},
{
'id': 3,
price: 150
},
{
'id': 2,
price: 110
},
{
'id': 3,
price: 120
},
{
'id': 2,
price: 100
}
];
var output = tours.reduce((acc, c) => {
acc[c.id] = acc[c.id] ? Math.min(c.price, acc[c.id]) : c.price;
return acc;
}, {});
console.log(output);
I have a application where I need count my doors. For example:
door1 has 60 people
door2 has 70 people
But how can I get the sum of each door because. I want push the sum of each door to my db, this data I get from my client every minute then I push the results to my db, and I clear the array for a next set of objects.
var a = [
{door: 1 , people: 20},
{door: 2 , people: 20},
{door: 1 , people: 10},
{door: 1 , people: 20},
{door: 2 , people: 50},
]
This a great example of when you can use map() and reduce() together:
Array.prototype.map() will run a function on each item in an array and transform each item into something else (returning a new array of the same length).
Array.prototype.reduce() will cumulatively calculate a single value based on each value in an array (returning just a single value).
var total = a.map(function(e) {
return e.people
})
.reduce(function(a, b) {
return {a + b};
})
In the example above we first use map() to transform each object in the array into just its 'people' value. So after this step we have an array that looks like:
[20, 20, 10, 20, 50]
Then we call reduce() on that array, which cumulatively adds the numbers together.
In ES6, this can be written even more succintly as:
let total = a.map(o => o.people).reduce((a,b) => a + b);
var a = [
{door: 1 , people: 20},
{door: 2 , people: 20},
{door: 1 , people: 10},
{door: 1 , people: 20},
{door: 2 , people: 50},
];
var sum = {};
for(var i=0;i<a.length;i++){
sum[a[i].door] = sum[a[i].door] || 0;
sum[a[i].door] += a[i].people;
}
console.log(sum);
var result = {}
a.map(i => result[i.door] = (result[i.door] || 0) + i.people)
and now just console.log(result)
Or you can even enhance the code using some syntactic sugar like that:
var result = {}
a.map({door, people} => result[i.door] = (result[door] || 0) + people)
values=[1,2].map(door=>a.filter(d=>d.door==door).reduce((val,d)=>val+d.people,0));
values will be [60,70],
For every door (1 and 2), get all elems of a where the elems door is door, then join these elems people value and map it back to the array. So every door is replaced with its referring value.
If you dont know the doors, you could create an associative object:
values=a.reduce((all,elem)=>{ return all[elem[door]]=all[elem.door]||0,all[elem.door]+=elem.people;},{});
this will result in:
{
1:60,
2:70
}
var array = [
{door: 1 , people: 20},
{door: 2 , people: 20},
{door: 1 , people: 10},
{door: 1 , people: 20},
{door: 2 , people: 50},
]
var res = [];
array.forEach(function(element) {
var e = res.find(function(data) {
return data.door == element.door;
});
if(e) {
element.people = e.people + element.people;
} else {
res.push({
door: element.door,
people: element.people
});
}
});
I don't know your desired result format, but if an object is good and taking into account you don't know about your doors beforehand, using just reduce() could be enough.
var a = [
{door: 1 , people: 20},
{door: 2 , people: 20},
{door: 1 , people: 10},
{door: 1 , people: 20},
{door: 2 , people: 50},
]
var result = a.reduce((accumulator, currentValue) => {
if (!accumulator[currentValue.door]) {
// set property the first time you find a door
accumulator[currentValue.door] = 0;
}
// sum the number of people each door in each iteration
accumulator[currentValue.door] += currentValue.people;
return accumulator;
}, {});
console.log(result)
The code above is easier to understand, but is a bit verbose. The callback of reduce() can be abbreviated like this:
a.reduce((accumulator, currentValue) => {
accumulator[currentValue.door] = accumulator[currentValue.door] || 0;
accumulator[currentValue.door] += currentValue.people;
return accumulator;
}, {});
I have an array of objects
list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}]
And I'm looking for an efficient way (if possible O(log(n))) to remove duplicates and to end up with
list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}]
I've tried _.uniq or even _.contains but couldn't find a satisfying solution.
Thanks!
Edit : The question has been identified as a duplicate of another one. I saw this question before posting but it didn't answer my question since it's an array of object (and not a 2-dim array, thanks Aaron), or at least the solutions on the other question weren't working in my case.
Plain javascript (ES2015), using Set
const list = [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }, { x: 1, y: 2 }];
const uniq = new Set(list.map(e => JSON.stringify(e)));
const res = Array.from(uniq).map(e => JSON.parse(e));
document.write(JSON.stringify(res));
Try using the following:
list = list.filter((elem, index, self) => self.findIndex(
(t) => {return (t.x === elem.x && t.y === elem.y)}) === index)
Vanilla JS version:
const list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];
function dedupe(arr) {
return arr.reduce(function(p, c) {
// create an identifying id from the object values
var id = [c.x, c.y].join('|');
// if the id is not found in the temp array
// add the object to the output array
// and add the key to the temp array
if (p.temp.indexOf(id) === -1) {
p.out.push(c);
p.temp.push(id);
}
return p;
// return the deduped array
}, {
temp: [],
out: []
}).out;
}
console.log(dedupe(list));
I would use a combination of Arrayr.prototype.reduce and Arrayr.prototype.some methods with spread operator.
1. Explicit solution. Based on complete knowledge of the array object contains.
list = list.reduce((r, i) =>
!r.some(j => i.x === j.x && i.y === j.y) ? [...r, i] : r
, [])
Here we have strict limitation on compared objects structure: {x: N, y: M}. And [{x:1, y:2}, {x:1, y:2, z:3}] will be filtered to [{x:1, y:2}].
2. Generic solution, JSON.stringify(). The compared objects could have any number of any properties.
list = list.reduce((r, i) =>
!r.some(j => JSON.stringify(i) === JSON.stringify(j)) ? [...r, i] : r
, [])
This approach has a limitation on properties order, so [{x:1, y:2}, {y:2, x:1}] won't be filtered.
3. Generic solution, Object.keys(). The order doesn't matter.
list = list.reduce((r, i) =>
!r.some(j => !Object.keys(i).some(k => i[k] !== j[k])) ? [...r, i] : r
, [])
This approach has another limitation: compared objects must have the same list of keys.
So [{x:1, y:2}, {x:1}] would be filtered despite the obvious difference.
4. Generic solution, Object.keys() + .length.
list = list.reduce((r, i) =>
!r.some(j => Object.keys(i).length === Object.keys(j).length
&& !Object.keys(i).some(k => i[k] !== j[k])) ? [...r, i] : r
, [])
With the last approach objects are being compared by the number of keys, by keys itself and by key values.
I created a Plunker to play with it.
One liners for ES6+
If you want to find uniq by x and y:
arr.filter((v,i,a)=>a.findIndex(t=>(t.x === v.x && t.y===v.y))===i)
If you want to find uniques by all properties:
arr.filter((v,i,a)=>a.findIndex(t=>(JSON.stringify(t) === JSON.stringify(v)))===i)
The following will work:
var a = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];
var b = _.uniq(a, function(v) {
return v.x && v.y;
})
console.log(b); // [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 } ]
Filter the array after checking if already in a temorary object in O(n).
var list = [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }, { x: 1, y: 2 }],
filtered = function (array) {
var o = {};
return array.filter(function (a) {
var k = a.x + '|' + a.y;
if (!o[k]) {
o[k] = true;
return true;
}
});
}(list);
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');
No libraries, and works with any depth
Limitation:
You must provide only string or Number properties as hash objects otherwise you'll get inconsistent results
/**
* Implementation, you can convert this function to the prototype pattern to allow
* usage like `myArray.unique(...)`
*/
function unique(array, f) {
return Object.values(
array.reduce((acc, item) => ({ ...acc, [f(item).join(``)]: item }), {})
);
}
const list = [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}, { x: 1, y: 2}];
// Usage
const result = unique(list, item => [item.x, item.y]);
// Output: [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}]
console.log(result);
Snippet Sample
// Implementation
function unique(array, f) {
return Object.values(
array.reduce((acc, item) => ({ ...acc, [f(item).join(``)]: item }), {})
);
}
// Your object list
const list = [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}, { x: 1, y: 2}];
// Usage
const result = unique(list, item => [item.x, item.y]);
// Add result to DOM
document.querySelector(`p`).textContent = JSON.stringify(result, null, 2);
<p></p>
With Underscore's _.uniq and the standard JSON.stringify it is a oneliner:
var list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];
var deduped = _.uniq(list, JSON.stringify);
console.log(deduped);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
However, this presumes that the keys are always specified in the same order. By sophisticating the iteratee, we can make the solution work even if the order of the keys varies. This problem as well as the solution also apply to other answers that involve JSON.stringify.
var list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {y:2, x:1}];
// Ensure that objects are always stringified
// with the keys in alphabetical order.
function replacer(key, value) {
if (!_.isObject(value)) return value;
var sortedKeys = _.keys(value).sort();
return _.pick(value, sortedKeys);
}
// Create a modified JSON.stringify that always
// uses the above replacer.
var stringify = _.partial(JSON.stringify, _, replacer, null);
var deduped = _.uniq(list, stringify);
console.log(deduped);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
For Lodash 4, use _.uniqBy instead of _.uniq.
Using lodash you can use this one-liner:
_.uniqBy(list, e => { return e.x && e.y })