linq.js to group by array of objects in javascript - javascript

I want to use linq.js to group the following data by date.
data2 = [{
"date": 1399298400.0,
"adId": 1057946139383,
"impressions": 1000000
}, {
"date": 1399298400.0,
"adId": 3301784671323,
"impressions": 535714
}...... etc.
];
Here's my attempt:
var linq = Enumerable.From(data2);
data2 = linq.GroupBy(function (x) {
return x.date;
}).Select(function (x) {
return {
date: x.Key(),
impressions: x.Sum(function (y) {
return y.impressions | 0;
})
};
}).ToArray();
However, It's not working correctly because the sum of all the impressions before and after the GroupBy are close but not identical.
What is the correct way to use group by in linq.js in this case?
Here's an example in fiddle with full dataset here which alerts the total impressions before and after using the GroupBy.

Solution
You can do this by passing a callback as the third parameter like this:
var grouped = Enumerable.from(dataArray).groupBy("$.person", null, (key, g) => {
return {
person: key,
likes: g.sum("$.likes | 0")
}
}).toArray()
Explanation
In groupBy, the third parameter allows you to modify the results before emitting:
In JS, the bitwise or operator (a single pipe |) returns the first value if it exists, otherwise it returns the second one. Without it, trying to sum an undefined value with a real one, will return NaN
undefined + 1 // NaN
Without | 0, the result would look like this:
This example uses shorthand syntax, but if you prefer anytime you see a string with a dollar sign, you can replace it with the lambda syntax like this (they both do the same exact thing):
// Shorthand
.Select("$.impressions")
// Lambda
.Select(function (x) { return x.impressions })
Working demo with Stack Snippets:
var dataArray = [
{
person: "james",
likes: 100
},
{
person: "james",
likes: 250
},
{
person: "kyle",
likes: 300
},
{
person: "kyle"
//,likes: 450
}
];
var grouped = Enumerable.from(dataArray).groupBy("$.person", null, (key, g) => {
return { person: key, likes: g.sum("$.likes | 0") }
}).toArray()
console.log(grouped);
<script src="https://unpkg.com/linq#3.2.0/linq.js"></script>
Further Reading:
SO - linqjs group by with a sum

I am the author of the open source project http://www.jinqJs.com. You can easily do that in jinqJs like this:
jinqJs().from(data2).groupBy('date').sum('impressions').select();
Let me know if I can be of anymore help.

You might try to group by date.toString(). Might be safer due to how JS evaluates dates equality

Alternatively, people coming into this question might have zero to a lot of buy in using linq.js.
If you're already pulling it in, go for it, but if this is the first couple real use cases for it, it's worth noting that you can accomplish the same thing in vanilla js:
For this data:
var dataArray = [
{ person: "james", likes: 100 },
{ person: "james", likes: 250 },
{ person: "kyle", likes: 300 },
{ person: "kyle" }
];
You can build an object with properties for each key / person and keep summing the values of those props
var obj = dataArray.reduce((acc, cur) => {
acc[cur.person] = (acc[cur.person] || 0) + (cur.likes || 0)
return acc
}, {})
If you want that to be an array of objects, you can convert from an object to array like this
var array = Object.entries(obj).map(entry => {
return { person: entry[0], likes: entry[1] }
})

Related

Calling function with a passed in array

I'm trying to write a function that will be called with an array that has information on a person such as their name and then age. I need this function to grab all of the numbers only and then return them then added up. I've done some research and it seems filter and reduce are what I need to do this in the easiest way for a total beginner like me to do?
Apologies for any typos/wrong jargon as my dyslexia gets the better of me sometimes.
An example of what kind of array is being passed into the function when called;
{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 },
Would return the total added numbers.
// returns 92
Why isn't the array I'm calling this function with working? Can you provide me a working example without the array being hardcoded like the other answers? - I'm passing in an array to the function. The main objective is to grab any number from the passed in array and add them together with an empty array returning 0.
function totalNums(person) {
person.reduce((a,b) => a + b, 0)
return person.age;
}
console.log(totalNums([]))
You need to save the result into a new variable then console.log() it like this
const arr= [{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 },...
];
function totalNums(person) {
let res = person.reduce((a,b) => a + b.age, 0)
return res;
}
console.log(totalNums(arr));
and this is why it has to be like that
.reduce()
js methods like .map(), .filter(), .reduce() and some others, they return a new array, they don't modify the original array.
You can console.log(arr); and you will get this output:
[{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 },...
];
Your original array unchanged even after running your function so in order to get the result you expect, you need to store it inside a new variable
You need to save the result of your reduce.
For example with array of numbers you would do:
function totalNums(person) {
let res = person.reduce((a,b) => a + b, 0)
return res;
}
console.log(totalNums([5,6,4]))
And for your example you would like to do something like this:
function totalNums(person) {
let res = person.reduce((a,b) => a + b.age, 0)
return res;
}
console.log(totalNums([
{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 }
]))
function totalNums(person) {
person.reduce((a,b) => a + b, 0)
return person.age;
}
console.log(totalNums([]))
Talking about the function you have created it is incorrect because:
return person.age; Here you are passing an array to function and then accessing it like it's an object.
person.reduce((a,b) => a + b, 0) you can't add a and b because b is an object.
You are not storing value which reduce function will return.
Solution Below :
The reduce function always returns something It never makes changes in the original array.
function totalNums(persons) {
const totalAge = persons.reduce((total, person) => total + person.age, 0);
return totalAge;
}
const persons = [
{ name: "Clarie", age: 22 },
{ name: "Bobby", age: 30 },
{ name: "Antonio", age: 40 },
];
console.log(totalNums(persons));
You can replace total and person with a and b respectively in the above code snippet for your reference.

Values from last FOR loop over all, after FOR is done

I have a function which get a json as parameter, build another json with some values from given json and return builded json.
function getMyJSON(json) {
var result = {
lastUpdate: "",
legends: null
};
result.legends = (new Array(json.legends.length)).fill({
name: null,
rgb: null,
values: null
});
for (let j = 0; j < json.legends.length; j++) {
result.legends[j].name = json.legends[j].name;
result.legends[j].rgb = json.legends[j].rgb;
result.legends[j].values = (new Array(10)).fill(0);
console.log(result.legends[0].name); //PRINT ONLY FIRST ELEMENT
}
console.log(result.legends);
return result;
}
The problem appear after for loop is done. All result.legends have the same value from the last json.legends
Here is how output look:
The legends.name of first element(result.legends[0].name) is changed after every loop.
At the end, all legends.name from result are equal with the last legends.name from json. Why?
I found on google that it is something about variable scope, but I can't figure it out how to do this.
You need independent objects inside of the array. Array#fill takes the same object reference and this leads to the same result in each object.
Instead of this, you could create a new array with Array.from and map new objects with the second parameter for a callback.
result.legends = Array.from(
{ length: json.legends.length },
_ => ({ name: null, rgb: null, values: null })
);
#NinaScholz has described the problem and solved it, however as I mentioned in the comments on the question you can improve and simplify the logic by using map():
var obj = {
legends: [{
name: 'foo',
rgb: 'C00'
},{
name: 'bar',
rgb: 'FFF'
},{
name: 'fizz',
rgb: 'CCFFCC'
},{
name: 'buzz',
rgb: '000000'
}]
}
console.log(getMyJSON(obj));
function getMyJSON(o) {
return {
lastUpdate: "",
legends: o.legends.map(function(item) {
return {
name: item.name,
rgb: item.rgb,
values: (new Array(10)).fill(0)
}
})
};
}

How to acess a complex JSON [duplicate]

This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
Closed 6 years ago.
I am a beginner and, as I began to increase the complexity of JSON, I started to confuse myself by accessing it. How do I access the following JSON? I would like to access the value and keys of the services
{
employers:{
Mike: {
old: 20,
services:{
cut : {
value : 10
}
hair_straightening : {
value: 20
}
}
}
Penny: {
old: 20,
services:{
cut : {
value : 10
}
hair_straightening : {
value: 20
}
}
}
}
}
Everyone has to start somewhere :)
I'm not sure if I completely understand what you're after, but here goes...
First off, it looks like your notation on your object is subtly off when listing your keys. For example, between Mike and Penny you should have a comma separating each of those keys in the larger employers object.
Something like:
employers: {
Mike: {
...
}, //need a comma here
Penny: {
...
}
}
Also, within each of those employers, there should be a comma between the keys for cut and hair_straightening.
services: {
cut: {
value: 10
}, //need a comma here
hair_straightening: {
value: 20
}
}
Now to your actual question...
To get the keys for each of the services, you can use Object.keys(). This function would get you they keys for a given employer. Then you can also grab the values from inside that same function. (Note this is for only one employer; you'd just want to iterate over both and use this same function on each)
function getServices(employer) {
var services = employer.services;
var servicesKeys = Object.keys(services);
var serviceValueMatrix = [];
servicesKeys.forEach(function(service) {
serviceValueMatrix.push([service, employer.services[service].value])
})
return serviceValueMatrix;
}
// assuming you had var yourJSON = { employers: {...} }
// getServices(yourJSON.employers.Mike);
// returns [["cut",10], ["hair_straightening",20]]
Also, given that your JSON object is already in key:value format, you could probably skip the last set of objects in the format of value: 10, value: 20 etc, and instead just make the last tier of your object something like:
services:{
cut: 10,
hair_straightening: 20
}
Then you could just grab services.cut and services.hair_straightening.
Full code below for clarity:
const yourJSON = {
employers: {
Mike: {
old: 20,
services: {
cut: {
value: 10
},
hair_straightening: {
value: 20
}
}
},
Penny: {
old: 20,
services: {
cut: {
value: 10
},
hair_straightening: {
value: 20
}
}
}
}
}
function getServices(employer) {
var services = employer.services;
var servicesKeys = Object.keys(services);
var serviceValueMatrix = [];
servicesKeys.forEach(function(service) {
serviceValueMatrix.push([service, employer.services[service].value])
})
return serviceValueMatrix;
}
console.log(getServices(yourJSON.employers.Mike));
// returns [["cut",10], ["hair_straightening",20]]
your json should look something like this:
{
employers:[
{
name: "Mike",
age: 20,
services: [
{name:"cut",value:10},
{name:"hair_straightening",value:20}
]
},{
name: "Penny",
age: 20,
services: [
{name:"cut",value:10},
{name:"hair_straightening",value:20}
]
}
]
}
You can use Object.keys to get the keys of an object as an array, then you can loop through that nicely.
// in this case json is a variable representing your parsed data
Object.keys(json).map(function(key) {
console.log(json[key])
return json[key].services
})
That would give you an array of services objects.

What are the reduceAdd, reduceSum , reduceRemove functions in crossfilter? How should they be used?

Can someone explain in simple terms how reduce function with its arguments reduceAdd, reduceSum, reduceRemove works in crossfilter?
Remember that map reduce reduces a dataset by keys of a particular dimension. For example lets use a crossfilter instance with records:
[
{ name: "Gates", age: 57, worth: 72000000000, gender: "m" },
{ name: "Buffet", age: 59, worth: 58000000000, gender: "m" },
{ name: "Winfrey", age: 83, worth: 2900000000, gender: "f" },
{ name: "Bloomberg", age: 71, worth: 31000000000, gender: "m" },
{ name: "Walton", age: 64, worth: 33000000000, gender: "f" },
]
and dimensions name, age, worth, and gender. We will reduce the gender dimension using the reduce method.
First we define the reduceAdd, reduceRemove, and reduceInitial callback methods.
reduceInitial returns an object with the form of the reduced object and the initial values. It takes no parameters.
function reduceInitial() {
return {
worth: 0,
count: 0
};
}
reduceAdd defines what happens when a record is being 'filtered into' the reduced object for a particular key. The first parameter is a transient instance of the reduced object. The second object is the current record. The method will return the augmented transient reduced object.
function reduceAdd(p, v) {
p.worth = p.worth + v.worth;
p.count = p.count + 1;
return p;
}
reduceRemove does the opposite of reduceAdd (at least in this example). It takes the same parameters as reduceAdd. It is needed because group reduces are updated as records are filtered and sometimes records need to be removed from a previously computed group reduction.
function reduceRemove(p, v) {
p.worth = p.worth - v.worth;
p.count = p.count - 1;
return p;
}
Invoking the reduce method would look like this:
mycf.dimensions.gender.reduce(reduceAdd, reduceRemove, reduceInitial)
To take a peek at the reduced values, use the all method. To see the top n values use the top(n) method.
mycf.dimensions.gender.reduce(reduceAdd, reduceRemove, reduceInitial).all()
The returned array would (should) look like:
[
{ key: "m", value: { worth: 161000000000, count: 3 } },
{ key: "f", value: { worth: 35000000000, count: 2 } },
]
The goals of reducing a dataset is to derive a new dataset by first grouping records by common keys, then reducing a dimension those groupings into a single value for each key. In this case we grouped by gender and reduced the worth dimension of that grouping by adding the values of records that shared the same key.
The other reduceX methods are convience methods for the reduce method.
For this example reduceSum would be the most appropriate replacement.
mycf.dimensions.gender.reduceSum(function(d) {
return d.worth;
});
Invoking all on the returned grouping would (should) look like:
[
{ key: "m", value: 161000000000 },
{ key: "f", value: 35000000000 },
]
reduceCount will count records
mycf.dimensions.gender.reduceCount();
Invoking all on the returned grouping would (should) look like:
[
{ key: "m", value: 3 },
{ key: "f", value: 2 },
]
Hope this helps :)
Source: https://github.com/square/crossfilter/wiki/API-Reference
http://blog.rusty.io/2012/09/17/crossfilter-tutorial/
var livingThings = crossfilter([
// Fact data.
{ name: “Rusty”, type: “human”, legs: 2 },
{ name: “Alex”, type: “human”, legs: 2 },
{ name: “Lassie”, type: “dog”, legs: 4 },
{ name: “Spot”, type: “dog”, legs: 4 },
{ name: “Polly”, type: “bird”, legs: 2 },
{ name: “Fiona”, type: “plant”, legs: 0 }
]);
For example, how many living things are in my house?
To do this, we’ll call the groupAll convenience function, which selects all
records into a single group, and then the reduceCount function, which
creates a count of the records.
// How many living things are in my house?
var n = livingThings.groupAll().reduceCount().value();
console.log("There are " + n + " living things in my house.") // 6
Now let’s get a count of all the legs in my house. Again, we’ll use the groupAll function to get all records in a single group, but then we call the
reduceSum function. This is going to sum values together. What values?
Well, we want legs, so let’s pass a function that extracts and returns the number of legs from the fact.
// How many total legs are in my house?
var legs = livingThings.groupAll().reduceSum(function(fact) {
return fact.legs;
}).value()
console.log("There are " + legs + " legs in my house.")
reduceCount function creates a count of the records.
reduceSum function is the sum values of these records.

Find duplicates in nested object

Assuming that I have a JS object wich contains other objects inside it, but all of them have the same structure.
I am trying to search inside them for any duplicated values.
For example:
{
{
id: "123"
name: "John"
surname: "Smith"
phone: "123456789"
},
{
id: "456"
name: "Jack"
surname: "Jones"
phone: "9876789123"
},
{
id: "789"
name: "John"
surname: "Doe"
phone: "123456789"
}
}
I want to search and find that the property 'phone' with value '123456789' is the same on both objects with ids '123' and '789'.
Note: the property/value combination that I am searching for is unknown beforehand.
First, if the outer structure is a plain Object, use an Array instead. If you can't change the structure, I'd convert it first.
DEMO: http://jsfiddle.net/3Y2qr/
if (!Array.isArray(data))
data = Object.keys(data)
.sort(function(a,b) { return a - b })
.map(function(key) { return data[key] })
Then you can reduce the set to groups of matches.
var groupByPhone = data.reduce(function(found, obj) {
if (found[obj.phone])
found[obj.phone].push(obj)
else
found[obj.phone] = [obj]
return found
}, {});
var dupes = Object.keys(groupByPhone)
.filter(function(key) {
return groupByPhone[key].length > 1
})
So now groupByPhone is an object where the keys are the unique phone numbers, and the values are the objects that have that number.
The dupes will be a list of phone number keys that can be used to lookup groups of duplicate objects in groupByPhone.
Array.prototype.filter to the rescue !
var searchFor = '123456789';
var filteredResult = arrayOfObjects.filter(function( obj ) {
return obj.phone === searchFor;
});
console.log( filteredResult );

Categories