Related
I have a problem! I am creating an rating app, and I have come across a problem that I don't know how to solve. The app is react native based so I am using JavaScript.
The problem is that I have multiple objects that are almost the same, I want to take out the average value from the values of the "same" objects and create a new one with the average value as the new value of the newly created object
This array in my code comes as a parameter to a function
var arr = [
{"name":"foo","value":2},
{"name":"foo","value":5},
{"name":"foo","value":2},
{"name":"bar","value":2},
{"name":"bar","value":1}
]
and the result I want is
var newArr = [
{"name":"foo","value":3},
{"name":"bar","value":1.5},
]
If anyone can help me I would appreciate that so much!
this is not my exact code of course so that others can take help from this as well, if you want my code to help me I can send it if that's needed
If you have any questions I'm more than happy to answer those
Iterate the array with Array.reduce(), and collect to object using the name values as the key. Sum the Value attribute of each name to total, and increment count.
Convert the object back to array using Object.values(). Iterate the new array with Array.map(), and get the average value by dividing the total by count:
const arr = [{"name":"foo","Value":2},{"name":"foo","Value":5},{"name":"foo","Value":2},{"name":"bar","Value":2},{"name":"bar","Value":1}];
const result = Object.values(arr.reduce((r, { name, Value }) => {
if(!r[name]) r[name] = { name, total: 0, count: 0 };
r[name].total += Value;
r[name].count += 1;
return r;
}, Object.create(null)))
.map(({ name, total, count }) => ({
name,
value: total / count
}));
console.log(result);
I guess you need something like this :
let arr = [
{name: "foo", Value: 2},
{name: "foo", Value: 5},
{name: "foo", Value: 2},
{name: "bar", Value: 2},
{name: "bar", Value: 1}
];
let tempArr = [];
arr.map((e, i) => {
tempArr[e.name] = tempArr[e.name] || [];
tempArr[e.name].push(e.Value);
});
var newArr = [];
$.each(Object.keys(tempArr), (i, e) => {
let sum = tempArr[e].reduce((pv, cv) => pv+cv, 0);
newArr.push({name: e, value: sum/tempArr[e].length});
});
console.log(newArr);
Good luck !
If you have the option of using underscore.js, the problem becomes simple:
group the objects in arr by name
for each group calculate the average of items by reducing to the sum of their values and dividing by group length
map each group to a single object containing the name and the average
var arr = [
obj = {
name: "foo",
Value: 2
},
obj = {
name: "foo",
Value: 5
},
obj = {
name: "foo",
Value: 2
},
obj = {
name: "bar",
Value: 2
},
obj = {
name: "bar",
Value: 1
}
]
// chain the sequence of operations
var result = _.chain(arr)
// group the array by name
.groupBy('name')
// process each group
.map(function(group, name) {
// calculate the average of items in the group
var avg = (group.length > 0) ? _.reduce(group, function(sum, item) { return sum + item.Value }, 0) / group.length : 0;
return {
name: name,
value: avg
}
})
.value();
console.log(result);
<script src="http://underscorejs.org/underscore-min.js"></script>
In arr you have the property Value and in newArr you have the property value, so I‘ll assume it to be value both. Please change if wished otherwise.
var map = {};
for(i = 0; i < arr.length; i++)
{
if(typeof map[arr[i].name] == ‘undefined‘)
{
map[arr[i].name] = {
name: arr[i].name,
value: arr[i].value,
count: 1,
};
} else {
map[arr[i].name].value += arr[i].value;
map[arr[i].name].count++;
}
var newArr = [];
for(prop in map)
{
map[prop].value /= map[prop].count;
newArr.push({
name: prop,
value: map[prop].value
});
}
delete map;
I'm trying to manipulate this sample array of objects.
[ { name: 'John Wilson',
id: 123,
classes: ['java', 'c++']},
{ name: 'John Wilson',
id: 123,
classes: 'uml'},
{ name: 'Jane Smith',
id: 321,
classes: 'c++'} ]
What I need to do is to merge objects with the same 'id', concatenating 'classes' and keeping one 'name'.
The result should be:
[ { name: 'John Wilson',
id: 123,
classes: ['java', 'c++', 'uml']},
{ name: 'Jane Smith',
id: 321,
classes: 'c++'} ]
I tried using .merge but it doesn't concatenate the values from 'classes', it just keeps the values from the last equal object.
What is the simplest way to do that, using lodash?
The function you're looking for is _.uniqWith, with a special twist which I will explain in a minute.
_.uniqWith is a lot like _.uniq in that it generates a unique array, but it allows you to pass your own custom comparator function that will be called to determine what counts as "equality."
Sane programmers would understand that this comparator should be side-effect free. The way this code works is by breaking that rule, and using a comparison function that does extra magic behind the scenes. However, this results in very concise code that will work no matter how many of these objects are in your array, so I feel like the transgression is well-justified.
I named the comparator function compareAndMerge so as not to hide its impure nature. It will merge both classes arrays and update the relevant property on both objects, but only if their id values are identical.
function merge(people) {
return _.uniqWith(people, compareAndMerge)
}
function compareAndMerge(first, second) {
if (first.id === second.id) {
first.classes = second.classes = [].concat(first.classes, second.classes)
return true
}
return false
}
var people = [{
name: 'John Wilson',
id: 123,
classes: ['java', 'c++']
}, {
name: 'John Wilson',
id: 123,
classes: 'uml'
}, {
name: 'Jane Smith',
id: 321,
classes: 'c++'
}]
console.log(merge(people))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
An aside: You were missing square brackets around your original classes lists. I made sure that the code above doesn't care whether or not the classes property holds a single string or an array of strings, though, just in case.
Using ES6 you can do so with a Map to hold the unique values, Array#reduce to populate it, and the spread operator with Map#values to convert it back to array:
const arr = [{"name":"John Wilson","id":123,"classes":["java","c++"]},{"name":"John Wilson","id":123,"classes":"uml"},{"name":"Jane Smith","id":321,"classes":"c++"}];
const result = [...arr.reduce((hash, { id, name, classes }) => {
const current = hash.get(id) || { id, name, classes: [] };
classes && (current.classes = current.classes.concat(classes));
return hash.set(id, current);
}, new Map).values()];
console.log(result);
Not sure using lodash... here's a way to do it with normal JS:
var combined = arr.reduce(function(a, item, idx) {
var found = false;
for (var i = 0; i < a.length; i++) {
if (a[i].id == item.id) {
a[i].classes = a[i].classes.concat(item.classes);
found = true;
break;
}
}
if (!found) {
a.push(item);
}
return a;
}, []);
Fiddle: https://jsfiddle.net/6zwr47mt/
use _.mergeWith to set merging customizer
_.reduce(data, function(result, item) {
item = _.mergeWith(
item,
_.find(result, {id: item.id}),
function(val, addVal) {
return _.isArray(val) ? _.concat(val, addVal) : val;
});
result = _.reject(result, {id: item.id})
return _.concat(result, item);
}, []);
The following algorithm is not the best one but at least I know what it does :-)
console.log(clean(data));
function clean (data) {
var i, x, y;
var clean = [];
var m = clean.length;
var n = data.length;
data.sort((x, y) => x.id - y.id);
for (i = 0; i < n; i++) {
y = data[i];
if (i == 0 || x.id != y.id) {
clean.push(x = clone(y)), m++;
} else {
clean[m - 1] = merge(x, y);
}
}
return clean;
}
function clone (x) {
var z = {};
z.id = x.id;
z.name = x.name;
z.classes = x.classes.slice();
return z;
}
function merge (x, y) {
var z = {};
z.id = x.id;
z.name = x.name;
z.classes = unique(
x.classes.concat(y.classes)
);
return z;
}
function unique (xs) {
var i, j, n;
n = xs.length;
for (i = 1; i < n; i++) {
j = 0; while (j < i && xs[i] !== xs[j]) j++;
if (j < i) swap(xs, i, n - 1), i--, n--;
}
return xs.slice(0, n);
}
function swap (xs, i, j) {
var x = xs[i];
xs[i] = xs[j];
xs[j] = x;
}
<script>
var data = [{
id: 123,
name: 'John Wilson',
classes: ['java', 'c++']
}, {
id: 123,
name: 'John Wilson',
classes: ['uml', 'java']
}, {
id: 321,
name: 'Jane Smith',
classes: ['c++']
}];
</script>
What is the best way to fill in missing properties in an array of objects, such as this example:
[
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
I need to add the missing properties with a null value, so that when I pass this array on to be rendered in something such as a HTML table or CSV file, everything lines up correctly. I was thinking of passing over the array twice, once to get all the possible properties, and a second time to add those missing properties with a null value to each object where it doesn't exist. Is there a better way to do this?
EDIT: I won't know what the keys are until I have the data, it's coming from an API and the keys are not always requested explicitly.
My final solution
Thanks all, it seems the two pass approach is indeed the best approach. After I started to write this using the examples provided, I realised that the order of the properties wasn't being maintained. This is how I achieved filling in the missing props, and maintaining the correct order. Any suggestions for potential improvements are welcome.
var fillMissingProps = function(arr) {
// build a list of keys in the correct order
var keys = [];
arr.forEach(function(obj) {
var lastIndex = -1;
Object.keys(obj).forEach(function(key, i) {
if (keys.includes(key)) {
// record the position of the existing key
lastIndex = keys.lastIndexOf(key);
if (lastIndex < i) {
// this key is in the wrong position so move it
keys.splice(i, 0, keys.splice(lastIndex, 1)[0]);
lastIndex = i;
}
} else {
// add the new key in the correct position
// after the previous existing key
lastIndex++;
keys.splice(lastIndex, 0, key);
}
});
});
// build a template object with all props set to null
// and in the correct position
var defaults = {};
keys.forEach(function(key) {
defaults[key] = null;
});
// and update the array by overwriting each element with a
// new object that's built from the template and the original object
arr.forEach(function(obj, i, arr) {
arr[i] = Object.assign({}, defaults, obj);
});
return arr;
};
/** TEST **/
var currentArray = [
{
website: 'http://www.unknown.com'
},
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
title: 'Mr',
name: 'Richard',
gender: 'Male',
number: '04321 666 555'
},
{
id: '003ABCDEFGHIJKL',
name: 'Harry',
website: 'http://www.harry.com',
mobile: '07890 123 456',
city: 'Brentwood',
county: 'Essex'
}
];
var newArray = fillMissingProps(currentArray);
for (var i = 0; i < newArray.length; i++) {
for (var prop in newArray[i]) {
console.log(prop + ": " + newArray[i][prop]);
}
console.log('---------');
}
Given that you don't know apriori which keys are supposed to exist, you have no choice but to iterate over the array twice:
// build a map of unique keys (with null values)
var keys = {}
array.forEach(el => Object.keys(el).forEach(k => keys[k] = null));
// and update the array by overwriting each element with a
// new object that's built from the null map and the original object
array.forEach((el, ix, a) => a[ix] = Object.assign({}, keys, el));
Use Array.prototype.map():
const arr = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com',
},
{
name: 'Richard',
number: '07777 666 555',
},
{
name: 'Harry',
website: 'http://www.harry.com',
},
];
const newArr = arr.map(x => (
arr.map(x => Object.keys(x))
.reduce((a, b) =>
(b.forEach(z => a.includes(z) || a.push(z)), a)
)
.forEach(
y => (x[y] = x.hasOwnProperty(y) ? x[y] : null)
), x)
);
console.log(newArr);
Here is a more interesting answer, its a tad fun one but it will build up your objects on the fly as new properties appear:
var currentArray = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number: '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
var newArray = []
function NewObject() {
}
for(var i = 0; i < currentArray.length; i++){
var nObj = new NewObject();
for(var prop in currentArray[i]){
if(!NewObject.hasOwnProperty(prop))
NewObject.prototype[prop] = null;
nObj[prop]=currentArray[i][prop];
}
newArray.push(nObj);
}
for(var i = 0; i < newArray.length; i++){
for(var prop in newArray[i]){
console.log(prop+ ": "+newArray[i][prop]);
}
console.log('---------');
}
It builds new objects from the ones you provide and adds new properties to the objects if they don't exist already.
This idea was more for curiosities sake tho so any comments would be interesting :)
You can get all keys and set all keys using for..of loop, .map() to iterate all Object.keys(), redefine original array
var arr = [{
name: 'Harry',
website: 'http://www.harry.com'
},{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
}, {
name: 'Richard',
number: '07777 666 555'
}];
for (var obj of arr) {
for (var key of Object.keys(obj)) {
arr = arr.map(o => (o[key] = o[key] || null, o))
}
};
console.log(arr);
Something like this could work:
for (var i = 0; i < arrayLength; i++) {
yourArray[i].name = yourArray[i].name || null;
yourArray[i].number = yourArray[i].number || null;
yourArray[i].website= yourArray[i].website|| null;
}
Say i have a collection of objects like so
var collection = [
{
name:"John",
age:12,
location:"Califonia",
gender:"Male"
},
{
name:"Jane",
age:18,
location:"New york",
gender:"Female"
}
]
it is obvious "location" is the object key with the longest character length.
But how can i get this dynamically as i dont know how collection will be structured in advance.
With these functions:
/**
* Get the longest key in an object.
*/
function longestKey(obj) {
return Object.keys(obj).reduce(function(all, current) {
return all.length > current.length ? all : current;
});
}
/**
* Get the object with the longest key from array of objects.
*/
function longestKeyObj(arr) {
return arr.reduce(function(all, current) {
return longestKey(all).length > longestKey(current).length ? all : current;
});
}
You can do:
const me = {
name: 'nabil',
occupation: 'programming',
dob: '1984-12-28',
};
const you = {
longestKey: 'blah!',
};
console.log('The object is: ', longestKeyObj([me, you]));
console.log('The key is: ', longestKey(longestKeyObj([me, you])));
Which outputs:
The object is: { longestKey: 'blah!' }
The key is: longestKey
Demo: https://repl.it/#kadimi/longestKey
if you are sure the items in the collection will always have the same structure you can simple do this.
var collection = [
{
name:"John",
age:12,
location:"Califonia",
gender:"Male"
},
{
name:"Jane",
age:18,
location:"New york",
gender:"Female"
}
]
function getMaxKeys(collection){
var keys = Object.keys(collection[0]);
var keyWithMaxWidth = keys.reduce(function(prev, current){
if(prev.length > current.length) return prev;
else return current
});
return keyWithMaxWidth;
}
getMaxKeys(collection) //location
you can get array object key of specific object by command :
Object.keys(<object>)
Make a loop for find longest key. In your problem you need loop for all object in array
The code below will solve your problem in optimal way:
var sorted = (Object.keys(collection[0])).sort(function(a, b){
return b.length - a.length;
});
sorted[0]; // Location
You can loop each object in the array and then loop each property in the object like so:
var collection = [
{
name:"John",
age:12,
location:"Califonia",
gender:"Male"
},
{
name:"Jane",
age:18,
location:"New york",
gender:"Female"
},
{
veryLong: "This key will definitely be the longest"
}
];
var stats = {
key: '',
valLen: 0
};
collection.forEach(function(obj){
for(var prop in obj){
if(obj[prop].length > stats.valLen){
stats.key = prop
stats.valLen = obj[prop].length
}
}
});
console.log(stats.key);
console.log(stats.valLen);
Each item in the array may have a different format.
This example will log: 'veryLong' as longest key and 39 as its length.
See this fiddle
Just loop through the keys with Object.keys(obj) and pick up the longest one, Use the following function:
var getLongestKey = function getLongestKey(obj) {
for (key in keys) {
if (keys[key].length > longest.length) {
longest = keys[key];
}
}
}
And this is a working demo, to get the longest key for all the objects in the array :
var collection = [{
name: "John",
age: 12,
location: "Califonia",
gender: "Male"
}, {
name: "Jane",
age: 18,
location: "New york",
gender: "Female"
}];
var getLongestKey = function getLongestKey(obj) {
for (key in keys) {
if (keys[key].length > longest.length) {
longest = keys[key];
}
}
}
for (i = 0; i < collection.length; i++) {
var obj = collection[i];
var keys = Object.keys(obj);
var longest = keys[0];
getLongestKey(obj);
alert("Longest key in Object " + i + " is : " + longest);
}
I have this data:
list = [
{name:'apple', category: "fruit", price: 1.22 },
{name:'pear', category: "fruit", price: 2.22 },
{name:'coke', category: "drink", price: 3.33 },
{name:'sprite', category: "drink", price: .44 },
];
And I'd like to create a dictionary keyed on category, whose value is an array that contains all the products of that category. My attempt to do this failed:
var tmp = {};
list.forEach(function(product) {
var idx = product.category ;
push tmp[idx], product;
});
tmp;
function dictionary(list) {
var map = {};
for (var i = 0; i < list.length; ++i) {
var category = list[i].category;
if (!map[category])
map[category] = [];
map[category].push(list[i].name); // add product names only
// map[category].push(list[i]); // add complete products
}
return map;
}
var d = dictionary(list); // call
You can test it on jsfiddle.