How to get object with largest property from array? - javascript

I have an array of objects, with each object containing other 'subobjects'. I need to return the subobject with the largest 'quality' property.
The code below logs out all subobjects. How do I only return the one with the largest quality?
var maxQuality = function(Arr) {
Arr.forEach(function(obj, index) {
Math.max.apply(Math, obj.products.map(function(subObj) {
console.log(subObj);
}))
});
},
store = [
{
products: [
{
quality: 1,
info: 'info 1'
},
{
quality: 2,
info: 'info 2'
},
{
quality: 3,
info: 'info 3'
}
]
}
],
maxQualityProduct = maxQuality(store);

You can do that using the reduce() method on the array
var products = [
{
quality: 1,
info: 'info 1'
},
{
quality: 2,
info: 'info 2'
},
{
quality: 3,
info: 'info 3'
}
];
var highest = products.reduce(function(prev, current) {
return prev.quality > current.quality ? prev : current
}, {});
console.log(highest);
Note that reduce takes two parameters - one is the callback and the second is the initial item that you start with called seed. In this case, since we are only checking a flat value, a seed of an empty object will work fine since when the property quality is taken from it it would return undefined and that would be less than any of the other products. However, for more complex structures or comparisons, you might need to give an actual item from the array as a seed.

Although the other .reduce answer probably gives you what you need, if the store array were to contain more than one object - not sure if that is something you will want/need - you could use this:
var maxQuality = function(storeArray) {
function highestQuality(prev, curr) {
return prev.quality > curr.quality ? prev : curr
}
return storeArray.map(function(obj) {
return obj.products.reduce(highestQuality)
}).reduce(highestQuality);
}

I guess that boils down to a simple Array.prototype.sort kind of thing:
products.sort(( a, b ) => b.quality - a.quality )[ 0 ];

You can use following snippet:
var maxQuality = function(Arr) {
var result = undefined;
for (var obj of store) {
for (var product of obj.products) {
if (!result || product.quality > result.quality) {
result = product;
}
}
}
return result;
},
store = [
{
products: [
{
quality: 1,
info: 'info 1'
},
{
quality: 2,
info: 'info 2'
},
{
quality: 3,
info: 'info 3'
}
]
}
],
maxQualityProduct = maxQuality(store);
console.log(maxQualityProduct);

Related

How to remove any objects that appear more than once in an array of objects?

If I have an array like:
[
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
]
And I want to return an array that contains any objects that appear only once. So for this example, the desired output would be:
[
{
id: 1,
title: 'foo'
},
{
id: 4,
title: 'bantz'
}
]
I have tried a few different approaches that I have found to solve this using reduce() and indexOf(), like this solution, but they do not work with objects for some reason.
Any assistance would be greatly appreciated.
You could use a Map to avoid having to look through the array again and again, which would lead to inefficient O(n²) time-complexity. This is O(n):
function getUniquesOnly(data) {
return Array.from(
data.reduce( (acc, o) => acc.set(o.id, acc.has(o.id) ? 0 : o), new Map),
(([k,v]) => v)
).filter( x => x );
}
var data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
console.log(getUniquesOnly(data));
Do something like this:
const data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a, index) =>
result.concat(arr.some(b => a !== b && isEqual(a, b)) ? [] : a)
, []);
console.log(unique(data));
In this case, we loop through each element to reduce(), and before we add it, we see if another version of it exists in the array before adding it. We have to make sure that we are also not being equal without ourselves (otherwise we'd get an empty array).
isEqual() is a separate function to make it easy to customize what "equal" means.
As written, each element in data is unique, they're all separate objects. data[0] === data[4] is false, even though they have the same data. You must compare the data inside to determine if they're duplicates or not. As Paulpro mentioned earlier, {} === {} is also false, because they're two different objects, even though their values are the same.
console.log({} === {});
console.log({ a: 1 } === { a: 1 });
In the example version of isEqual(), I considered them equal if they had the same id.
Answer to previous version of the question
Do something like this:
const data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a) =>
result.concat(result.some(b => isEqual(a, b)) ? [] : a)
, []);
console.log(unique(data));
I split isEqual() to it's own function so you could easily define what "equal" means. As someone pointed out, technically all of those are unique, even if the data is different. In my example, I defined equal ids to mean equal.
I then use reduce to go through each and build an object. Before I add it to the array (via concat()), I loop through all of them with some() and go until either I find one that is equal (which I wouldn't include) or none are equal and I add it.
A straightforward implementation would look something like this:
Create an empty set (in this case an array) to contain unique values by whatever metric you define (I.E. deep comparison or comparing by a unique value like the "id")
Loop over the list of values
Whenever you find a value that is not contained within the set of unique values, add it
That is essentially how the solution you posted works, except that all of the values in your array are -- in JavaScript's eyes -- unique. Because of this you need to define your own way to compare values.
The .reduce method can be used like so:
function areEqual(a, b) { /* define how you want the objects compared here */ }
function contains(a, lst) {
return lst.reduce(function (acc, x) {
return acc || areEqual(a, x);
}, false);
}
function getUnique(lst) {
return lst.reduce(function (acc, x) {
if(!contains(x, acc))
{
acc.push(x);
}
return acc;
}, []);
}
You may want to look at how JavaScript object comparison works. For deep comparison specifically (which it sounds like you want) I would look at existing answers.

How to combine _.map and _.filter in a more efficient way?

I am using Lodash in my Angular project and I was wondering if there is a better way to write the following code:
$scope.new_arr = _.map(arr1, function(item){
return _.assign(item, {new_id: _.find(arr2, {id: item.id})});
});
$scope.new_arr = _.filter($scope.new_arr, function (item) {
return item.new_id !== undefined;
});
I am trying to combine values from one array to same objects in other array, and I want to ignore the objects that not appear in both arrays (it is something like join or left outer join in the sql language).
Here is a fiddle with an example of this code: Click me!
i think is better to use chaining
$scope.new_arr = _.chain(arr1)
.map(function(item) {
return _.merge(
{}, // to avoid mutations
item,
{new_id: _.find(arr2, {id: item.id})}
);
})
.filter('new_id')
.value();
https://jsfiddle.net/3xjdqsjs/6/
try this:
$scope.getItemById = (array, id) => {
return array.find(item => item.id == id);
};
$scope.mergeArrays = () => {
let items_with_ids = arr1.filter(item => !_.isNil($scope.getItemById(arr2,item.id)));
return items_with_ids.map(item => _.assign(item, {new_id: $scope.getItemById(arr2,item.id)}));
};
The answers provided here are all runtime of O(n^2), because they first run an outer loop on the first array, with an inner loop on the second array. You can instead run this in O(n). First, create a hashmap of all the ids in arr2 in a single loop; this will allow us an order 1 lookup. In the second loop on arr1, check this hashmap to determine if those items exist with O(n). Total Complexity is n + n = 2n, which is just O(n).
// provision some test arrays
var arr1 = [
{
id: 2
},
{
id: 4
},
{
id: 6
}
]
var arr2 = [
{
id: 3
},
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
// First, we create a map of the ids of arr2 with the items. Complexity: O(n)
var mapIdsToArr2Items = _.reduce(arr2, function(accumulator, item) {
accumulator[item.id] = item;
return accumulator;
}, {});
// Next, we use reduce (instead of a _.map followed by a _.filter for slightly more performance.
// This is because with reduce, we loop once, whereas with map and filter,
// we loop twice). Complexity: O(n)
var combinedArr = _.reduce(arr1, function(accumulator, item) {
// Complexity: O(1)
if (mapIdsToArr2Items[item.id]) {
// There's a match/intersection! Arr1's item matches an item in arr 2. Include it
accumulator.push(item);
}
return accumulator;
}, []);
console.log(combinedArr)
You could first make a Map with arr1 and then map the items of arr2 with the properties of arr1.
var arr1 = [{ id: 1, title: 'z' }, { id: 2, title: 'y' }, { id: 3, title: 'x' }, { id: 4, title: 'w' }, { id: 5, title: 'v' }],
arr2 = [{ id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'd' }, { id: 5, name: 'e' }],
map = new Map(arr1.map(a => [a.id, a])),
result = arr2.map(a => Object.assign({}, a, map.get(a.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Find parent object with max value

I'm new to javascript, so this question may sound very basic.
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
I am trying to write a function to return who's got max number of shirts. So I write two functions like this to first get the max values
var shirtValues = data.map(function(obj) {return obj.items[shirts]; });
var maxValue = Math.max.apply(null, shirtValues);
console.log(maxValue);
Now i need to find who is the person that got max number of shirts. How to achieve that?
I would start by changing your data structure. It's hard to see how the current format would be overly useful for iterating, aggregating, getting the user's name, getting the item's names, etc., without excessive iterating.
Here is one alternative data structure that is much easier to work with because it doesn't have nested arrays and doesn't require Object.keys to access data you need consistently (e.g. user name):
var data = [{
user: 'Amy',
items: {
shirt: 12,
trouser: 10
}
}, {
user: 'Bill',
items: {
shirt: 10,
trouser: 11
}
}, {
user: 'Chet',
items: {
shirt: 11,
trouser: 12
}
}];
With this format, you could easily sort by a particular item quantity:
let getTopItem = (data, itemName) => {
// 1. clone the array using concat
// 2. sort by value at supplied itemName
// 3. return the first item
let sorted = data.concat().sort((a, b) => {
return b.items[itemName] - a.items[itemName];
});
return sorted.shift();
}
let topShirts = getTopItem(data, 'shirt');
console.log(topShirts.user);
EDIT - I don't mean this negatively toward any of the answers as they all seem to be correct and useful approaches to getting the required data from the presented data structure - but look at how many iterations they all require to get this very basic data out of your object. Choosing the right structure for your data will save you a lot of headaches down the road.
Provided that, you cannot change your datastructure, reduce function can be very handy to serve your purpose. Actually, the logic becomes pretty straight-forward!. The code is provided below:
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
var new_data = data.reduce(function(max, obj) {
var obj_val;
var max_val;
max_val = get_shirt_val(max);
obj_val = get_shirt_val(obj);
return obj_val > max_val ? obj : max;
});
function get_shirt_val(obj) {
key = Object.keys(obj)[0]
return obj[key].items[0].shirt;
}
console.log(JSON.stringify(new_data));
console.log(Object.keys(new_data)[0])
Hope this helps!
If your JSON structure is this always. Then you can use this way to find maximum shirts count :
var max=0;
var maxShirtCount=null;
data.forEach(function(ele,ind){
currVal = ele[Object.keys(ele)[0]].items[0].shirt;
if(currVal>max){
max=currVal;
maxShirtCount=ele;
}
});
console.log(maxShirtCount);
for maximum trousers count :
var max=0;
var maxTrouserCount=null;
data.forEach(function(ele,ind){
currVal = ele[Object.keys(ele)[0]].items[1].trouser;
if(currVal>max){
max=currVal;
maxTrouserCount=ele;
}
});
console.log(maxTrouserCount);
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
function doSort(a, b){
return a[Object.keys(a)].items[0].shirt < b[Object.keys(b)].items[0].shirt;
}
console.log(Object.keys(data.sort(doSort)[0])[0]);
You could get the key, as name, get for the wanted item the count and make a comparison with the count of the previous persons. If max is the same, then extend the result array, if greater, then return a new object with the max data.
function getMax(item) {
return data.reduce(function (r, a) {
var name = Object.keys(a)[0],
count = a[name].items.reduce(function (s, b) {
return s + (b[item] || 0);
}, 0);
if (count >= r.count) {
if (count > r.count) {
return { item: item, names: [name], count: count };
}
r.names.push(name);
}
return r;
}, { item: item, names: [], count: 0 });
}
var data = [{ Amy: { items: [{ shirt: 12 }, { trouser: 10 }] } }, { Bill: { items: [{ shirt: 10 }, { trouser: 11 }] } }, { Chet: { items: [{ shirt: 11 }, { trouser: 12 }] } }],
maxShirt = getMax('shirt');
console.log(maxShirt); // all names with max count
console.log(maxShirt.names); // just the names
Without sorting and with a single pass reduce you might do as follows;
var data = [{ Amy: {items:[{shirt: 12},{trouser: 10}] } }, { Bill: {items:[{shirt: 10},{trouser: 11}] } }, { Chet: {items:[{shirt: 11},{trouser: 12}] } }
],
result = data.reduce((p,c) => { var sc = c[Object.keys(c)[0]].items[0].shirt;
return sc > p[0] ? [sc,c] : p;
},[0,{}])[1];
console.log(result);

Create new array from iterating JSON objects and getting only 1 of its inner array

See jsfiddle here: https://jsfiddle.net/remenyLx/2/
I have data that contains objects that each have an array of images. I want only the first image of each object.
var data1 = [
{
id: 1,
images: [
{ name: '1a' },
{ name: '1b' }
]
},
{
id: 2,
images: [
{ name: '2a' },
{ name: '2b' }
]
},
{
id: 3
},
{
id: 4,
images: []
}
];
var filtered = [];
var b = data1.forEach((element, index, array) => {
if(element.images && element.images.length)
filtered.push(element.images[0].name);
});
console.log(filtered);
The output needs to be flat:
['1a', '2a']
How can I make this prettier?
I'm not too familiar with JS map, reduce and filter and I think those would make my code more sensible; the forEach feels unnecessary.
First you can filter out elements without proper images property and then map it to new array:
const filtered = data1
.filter(e => e.images && e.images.length)
.map(e => e.images[0].name)
To do this in one loop you can use reduce function:
const filtered = data1.reduce((r, e) => {
if (e.images && e.images.length) {
r.push(e.images[0].name)
}
return r
}, [])
You can use reduce() to return this result.
var data1 = [{
id: 1,
images: [{
name: '1a'
}, {
name: '1b'
}]
}, {
id: 2,
images: [{
name: '2a'
}, {
name: '2b'
}]
}, {
id: 3
}, {
id: 4,
images: []
}];
var result = data1.reduce(function(r, e) {
if (e.hasOwnProperty('images') && e.images.length) r.push(e.images[0].name);
return r;
}, [])
console.log(result);
All answers are creating NEW arrays before projecting the final result : (filter and map creates a new array each) so basically it's creating twice.
Another approach is only to yield expected values :
Using iterator functions
function* foo(g)
{
for (let i = 0; i < g.length; i++)
{
if (g[i]['images'] && g[i]["images"].length)
yield g[i]['images'][0]["name"];
}
}
var iterator = foo(data1) ;
var result = iterator.next();
while (!result.done)
{
console.log(result.value)
result = iterator.next();
}
This will not create any additional array and only return the expected values !
However if you must return an array , rather than to do something with the actual values , then use other solutions suggested here.
https://jsfiddle.net/remenyLx/7/

Sort object-array and additionally a nested array in these objects

Updated example, as it didn't match to the object structure which was given in the post
There is an array, which has multiple objects like this:
{
text: text,
elements: [
{
id: id,
page: pageNumber
}
]
}
Now I need to sort the content in two ways:
Sort all array elements by the text-field
I would do like this:
array.sort(function(a,b) {
if (a.text < b.text) return -1;
if (a.text > b.text) return 1;
return 0;
});
The content of the nested elements-array should also be sorted by the page-field.
I don't know how to sort an array, which is nested in the objects... Unfortunately it gets more difficult as the elements are strings, but represent some page numbers. So how can I sort these data in a correct way, as elements could look like 123 or 23-34? And in the case 123-125, 23-34, the last element should be the first.
Example
[
{
text: 'Some text',
elements: [
{ id: 1, pages: '45-67' },
{ id: 2, pages: '12-34' }
]
},
{
text: 'Another text',
elements: [
{ id: 3, pages: '12-34' }
]
}
]
Should be sorted to:
[
{
text: 'Another text',
elements: [
{ id: 3, pages: '12-34' }
]
},
{
text: 'Some text',
elements: [
{ id: 2, pages: '12-34' },
{ id: 1, pages: '45-67' }
]
},
]
So the object order has changed, as A is before S and the order of the page elements in the (now) second object are ordered the other way round.
For the first part, you could use the power of String#localeCompare. And use for the page array another sort function with splitting the string.
var array = [{ id: 1, text: 'Some text', elements: [{ id: 1, pages: '45-67' }, { id: 2, pages: '12-34' }, { id: 4, pages: '12-5' }, { id: 5, pages: '12' }] }, { id: 3, text: 'Another text', elements: [{ id: 3, pages: '12-34' }] }];
array.sort(function (a, b) {
return a.text.localeCompare(b.text);
});
array.forEach(function (el) {
el.elements.sort(function (a, b) {
var aa = a.pages.split('-'),
bb = b.pages.split('-');
return aa[0] - bb[0] || (aa[1] || 0) - (bb[1] || 0);
});
});
console.log(array);
The problem is not a difficult one, as the author has already broken down the problem into smaller chunks. For instance - you mentioned sort the text first and then the nested property (pages), which you are having trouble with.
Your use case is easier as we know you are dealing with pages, so the page numbers will be consistent. E.g. It is unlikely to have entry like '10-20' and '10-15', well if they do then my code below will have to expand a bit to handle this sort of situation.
Otherwise what I have done below is to split the string entry using the - character as delimeter to get a fromPage and toPage parameter. Then using the fromPage we do the usual compare in the sort function to determine who is smaller.
Once that is sorted, then I sort the outer items which you already got a solution for it.
Solution:
// Sort the nested properties first - in this instance it will be page
example.forEach(item => {
item.pages.sort(function (a, b) {
aFromPage = a.split('-')[0];
bFromPage = b.split('-')[0];
// Assuming from page number is unique, if it is not unique then the ToPage number can be
// used for comparison also.
return (aFromPage < bFromPage) ? -1 : 1;
})
});
// Handle the outer item
example.sort(function(a,b) {
if (a.text < b.text) return -1;
if (a.text > b.text) return 1;
return 0;
});
console.log(JSON.stringify(example));
Output:
[
{
"id" : 3,
"text" : "Another text",
"pages":
[
"1-11",
"12-34",
"35-100"
]
},
{
"id" : 1,
"text" : "Some text",
"pages":
[
"12-34",
"35-44",
"45-67",
"68-100"
]
}
]
there is a way to sort element.
function transElement () {
return parseInt(s.split('-')[0], 10);
}
elements.sort(function (a, b) {
return transElement(b) - transElement(a);
});

Categories

Resources