Related
I need to know how many matches I have between people.compare array and names array, then create a new object including this data.
names = [ 'Juan', 'Old']
people = [
{compare: ['Juan', 'Old'], Age: 70},
{compare: ['Juan', 'Young'], Age: 20}
]
Expected output:
peopleNew = [
{compare: ['Juan', 'Old'], 'Age': 70, 'MATCHES': 2},
{compare: ['Juan', 'Young'], 'Age': 20, 'MATCHES': 1}
]
This code loops through each array and uses Array.includes() to check each item for equality:
const peopleNew = people.map(obj => {
let matches = 0;
obj.compare.forEach(name => {
if (names.includes(name)) {
matches++; // Increase count by 1
}
})
// Create new object from original with new 'matches' property
return Object.assign({}, obj, { matches });
});
I recently started using Ramda and trying to find a pointfree way to write a method to reduce an array of objects.
Here is the array of object :
const someObj = [
{
name: 'A',
city: 1,
other: {
playtime: 30
}
},
{
name: 'B',
city: 2,
other: {
playtime: 20
}
},
{
name: 'c',
city: 1,
other: {
playtime: 20
}
}
];
What I am trying is to reduce the object using ramda in poinfree style like
{
'1': {
count: 2,
avg_play_time: 20 + 30 / count
},
'2': {
count: 1,
avg_play_time: 20 / count
}
}
I can do it using an array reduce method but not sure how can I write the same in ramda pointfree style. Any suggestion will be appreciated.
One solution would be to do something like this:
// An optic to extract the nested playtime value
// Coupled with a `lift` operation which allows it to be applied over a collection
// Effectively A -> B => A[] -> B[]
const playtimes = R.lift(R.path(['other', 'playtime']))
R.pipe(
// Group the provided array by the city value
R.groupBy(R.prop('city')),
// Return a body specification which computes each property based on the
// provided function value.
R.map(R.applySpec({
count: R.length,
average: R.pipe(playtimes, R.mean)
}))
)(someObj)
Ramda also has another function called R.reduceBy which provides something inbetween reduce and groupBy, allowing you to fold up values with matching keys together.
So you can create a data type like the following that tallies up the values to averaged.
const Avg = (count, val) => ({ count, val })
Avg.of = val => Avg(1, val)
Avg.concat = (a, b) => Avg(a.count + b.count, a.val + b.val)
Avg.getAverage = ({ count, val }) => val / count
Avg.empty = Avg(0, 0)
Then combine them together using R.reduceBy.
const avgCities = R.reduceBy(
(avg, a) => Avg.concat(avg, Avg.of(a.other.playtime)),
Avg.empty,
x => x.city
)
Then pull the average values out of the Avg into the shape of the final objects.
const buildAvg = R.applySpec({
count: x => x.count,
avg_play_time: Avg.getAverage
})
And finally pipe the two together, mapping buildAvg over the values in the object.
const fn = R.pipe(avgCities, R.map(buildAvg))
fn(someObj)
I would write it like this, hope it helps!
const stats = R.pipe(
R.groupBy(R.prop('city')),
R.map(
R.applySpec({
count: R.length,
avg_play_time: R.pipe(
R.map(R.path(['other', 'playtime'])),
R.mean,
),
}),
),
);
const data = [
{ name: 'A', city: 1, other: { playtime: 30 } },
{ name: 'B', city: 2, other: { playtime: 20 } },
{ name: 'c', city: 1, other: { playtime: 20 } },
];
console.log('result', stats(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
Here's another suggestion using reduceBy with mapping an applySpec function on each property of the resulting object:
The idea is to transform someObj into this object using getPlaytimeByCity:
{ 1: [30, 20],
2: [20]}
Then you can map the stats function on each property of that object:
stats({ 1: [30, 20], 2: [20]});
// { 1: {count: 2, avg_play_time: 25},
// 2: {count: 1, avg_play_time: 20}}
const someObj = [
{ name: 'A',
city: 1,
other: { playtime: 30 }},
{ name: 'B',
city: 2,
other: { playtime: 20 }},
{ name: 'c',
city: 1,
other: { playtime: 20 }}
];
const city = prop('city');
const playtime = path(['other', 'playtime']);
const stats = applySpec({count: length, avg_play_time: mean});
const collectPlaytime = useWith(flip(append), [identity, playtime]);
const getPlaytimeByCity = reduceBy(collectPlaytime, [], city);
console.log(
map(stats, getPlaytimeByCity(someObj))
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {prop, path, useWith, flip, append, identity, applySpec, length, mean, reduceBy, map} = R;</script>
I like all the other answers given so far. So naturally I want to add my own. ;-)
Here is a version that uses reduceBy to keep a running track of the count and mean. This would not work if you were looking for the median value or some other statistic, but given a count, an average, and a new value, we can calculate the new count and average directly. This allows us to iterate the data only once at the expense of doing some arithmetic on every iteration.
const transform = reduceBy(
({count, avg_play_time}, {other: {playtime}}) => ({
count: count + 1,
avg_play_time: (avg_play_time * count + playtime) / (count + 1)
}),
{count: 0, avg_play_time: 0},
prop('city')
)
const someObj = [{city: 1, name: "A", other: {playtime: 30}}, {city: 2, name: "B", other: {playtime: 20}}, {city: 1, name: "c", other: {playtime: 20}}]
console.log(transform(someObj))
<script src="https://bundle.run/ramda#0.26.1"></script>
<script>
const {reduceBy, prop} = ramda
</script>
This is not point-free. Although I'm a big fan of point-free style, I only use it when it's applicable. I think seeking it out for its own sake is a mistake.
Note that the answer from Scott Christopher could easily be modified to use this sort of calculation
I have an array of objects that contain a date and an amount (among other things).
There is an object for each date with a specific amount, but there can also be multiple objects for the same date containing different amounts.
I'd like to consolidate the objects so I only have one object in the array for each date ... and have the amount corresponding to that date be the total sum of all previous amounts in those objects.
Examples will probably help here:
What my array looks like now:
[
{
date: "2019-1-1", // this is a dupe date
amount: 20,
...
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
What I would like my array to look like:
[
{
date: "2019-1-1", // this is now a unique date
amount: 60, // and the amount was totaled
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
Use .reduce to reduce an array into an object (or into anything else) by iterating over its properties. You just need to test to see if an object with a matching date already exists in the accumulator first:
const input = [
{
date: "2019-1-1", // this is a dupe date
amount: 20,
foo: 'bar',
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
foo: 'bar',
},
{
date: "2019-1-2",
amount: 40,
foo: 'bar',
},
{
date: "2019-1-3",
amount: 40,
foo: 'bar',
}
];
const output = input.reduce((accum, item) => {
const { date, amount } = item;
const foundObj = accum.find(({ date: findDate }) => findDate === date);
if (foundObj) {
foundObj.amount += amount;
return accum;
}
accum.push(item);
return accum;
}, []);
console.log(output);
You may do as follows;
var data = [ { date: "2019-1-1", // this is a dupe date
amount: 20},
{ date: "2019-1-1", // this is a dupe date
amount: 40},
{ date: "2019-1-2",
amount: 40},
{ date: "2019-1-3",
amount: 40}
],
result = Object.values(data.reduce((r,d) => r[d.date] ? (r[d.date].amount += d.amount, r)
: (r[d.date] = d, r), {}));
console.log(result);
Regarding a comment i guess i have to explain this a little for those who may not be familiar with some ES6 functionalities.
Object.values() is a Object method which returns all own property values in an array.
So we are reducing our objects into an hash object of which we collect the properties by Object.values() later. While reducing we check if the currently examined object's date value exists as key in our map. If not we create that key and insert the examined object at that key position, if yes then we increment the previously inserted objects amount property by the value of currently examined objects amount value.
If you don't want to mutate the original data then please change r[d.date] = d into r[d.date] = Object.assign({},d).
The way I would do it is to create an object with the dates as the key, then you can iterate over the array and create a new date property if it doesn't exist or increase the amount if it does, then convert it back into an array:
const items = data.reduce((acc, curr) => {
if (!acc[curr.date]) { // basically creating a property with the date as the key and the value is the current object
acc[curr.date] = { ...curr };
} else { // if it exists already, then just increment the amount
acc[curr.date].amount += curr.amount;
}
return acc;
}, {});
const newArray = Object.values(items); // grab all the values from the object above
How do you get the data from distinct values also sum the total values from the same unique row
Here is the JSON Data result
As you can see they have the same full name. How do you get the distinct value while sum the total priceGross.
The result should be returning
eg. "Khalem Williams" and adding the total priceGross " 1200 + 1200 " = 2400
result return
FullName : "Khalem Williams"
TotalPriceGross: "2400"
I'm getting the distinct values with the code below but I'm wondering how to sum the total priceGross while also getting the distinct values.
var countTotal = [];
$.each(data.aaData.data,function(index, value){
if ($.inArray(value.invoiceToFullName, countTotal)==-1) {
countTotal.push(value.invoiceToFullName);
}
});
I would honestly suggest converting your data to an object with names as keys (unique) and total gross prices as values:
{
"Khalem Williams": 2400,
"John Doe": 2100
}
However, I have included in the snippet below example code for how to convert your data to an array of objects as well. I used a combination of Object.keys, Array#reduce and Array#map.
var data = {
aaData: {
data: [{
invoiceToFullName: "Khalem Williams",
priceGross: 1200
},
{
invoiceToFullName: "Khalem Williams",
priceGross: 1200
},
{
invoiceToFullName: "John Doe",
priceGross: 500
},
{
invoiceToFullName: "John Doe",
priceGross: 1600
},
]
}
}
var map = data.aaData.data.reduce(function(map, invoice) {
var name = invoice.invoiceToFullName
var price = +invoice.priceGross
map[name] = (map[name] || 0) + price
return map
}, {})
console.log(map)
var array = Object.keys(map).map(function(name) {
return {
fullName: name,
totalPriceGross: map[name]
}
})
console.log(array)
Just created my own dummy data resembling yours hope you get the idea, if not you can paste your json and i can adjust my solution, was just lazy to recreate your json.
Your problem seems to be a typical group by field from sql.
Sample data:
var resultsData = [{name: 'sipho', amount: 10}, {name: 'themba', amount: 30}, {name: 'sipho', amount: 60}, {name: 'naidoo', amount: 30}, {name: 'gupta', amount: 70}, {name: 'naidoo', amount: 10}];
Get value of customer(field) you going to group by:
var customers = $.unique(resultsData.map(function(value){return value.name}));
Go through each customer and add to their sum:
var desiredSummary = customers.map(function(customer){
var sum =0;
$.each(test,function(index, value){sum+=(value.name==customer?value.amount:0)});
return {customer: customer, sum: sum}
});
Hope it helps, Happy coding
I am trying to sort through some JSON to create a list of the data where only certain rows are shown. I have some code that displays the data the way I want to display it, it just isn't sorted the way I want it. Here is the working code for the pre-sorted data:
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<style type="text/css">
.items {display:table;list-style:none;margin:0;padding:0;border-spacing:5px;}
.items li {display:table-row;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;border:1px solid #ccc;padding:5px;margin:0 0 10px 0;}
.items li img {display:table-cell;vertical-align:top;width:160px;height:120px;}
.items li span.meta {display:table-cell;vertical-align:top;margin:0;padding:0 0 0 5px;font-size:6;}
.items li {margin:0 0 5px 0;}
</style>
<button onclick="mySearch()" type="button">Search</button>
<button onclick="myErase()" type="button">Clear Results</button>
<div id="home-list"></div>
<script type="text/javascript">
function mySearch() {
$('.items').remove();
//source file is https://docs.google.com/spreadsheet/ccc?key=1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2
$(function listHomes() {
$.getJSON( "https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values?alt=json-in-script&callback=?",
//this is the data that I wish to sort
function (data) {
$('div#home-list').append('<ul class="items"></ul>');
$.each(data.feed.entry, function(i,entry) {
var item = '<span style="display:none">' + entry.id.$t + '</span>';
item += '<img src="http://img.paragonrels.com/ParagonImages/Listings/P2/CGMLS/' + entry.gsx$mls.$t + '/0/960/720/4d8fbda25ff383c57b907de4c08a8677/CGMLS' + entry.gsx$mls.$t + '.JPG"/>';
item += '<span class="meta"><font size="3"><b>' + entry.title.$t + '</b></font>';
item += '<br/>City: ' + entry.gsx$city.$t;
item += '<br/>Bedrooms: ' + entry.gsx$beds.$t;
if (entry.gsx$subdivision.$t) {
item += '<br/>Description: ' + entry.gsx$subdivision.$t;
}
$('.items').append('<li>' + item + '</span></li>');
});
});
});
};
function myErase() {
$('.items').remove();
};
</script>
Here is the JSON
// API callback
JSONP({"version":"1.0","encoding":"UTF-8","feed":{"xmlns":"http://www.w3.org/2005/Atom","xmlns$openSearch":"http://a9.com/-/spec/opensearchrss/1.0/","xmlns$gsx":"http://schemas.google.com/spreadsheets/2006/extended","id":{"$t":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values"},"updated":{"$t":"2015-01-08T04:27:29.693Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"Source Page"},"link":[{"rel":"alternate","type":"application/atom+xml","href":"https://docs.google.com/spreadsheets/d/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/pubhtml"},{"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values"},{"rel":"http://schemas.google.com/g/2005#post","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values"},{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values?alt\u003djson-in-script"}],"author":[{"name":{"$t":"joshuam.hess"},"email":{"$t":"joshuam.hess#yourkickstart.org"}}],"openSearch$totalResults":{"$t":"4"},"openSearch$startIndex":{"$t":"1"},"entry":[{"id":{"$t":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/cokwr"},"updated":{"$t":"2015-01-08T04:27:29.693Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"303 Tarpon Trace \u003cbr\u003eByron, GA 31008"},"content":{"type":"text","$t":"mls: 122445, state: GA, county: Bibb County, city: Macon, subdivision: None, high: Northside, beds: 4, baths: 3, price: 250000, cars: 3"},"link":[{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/cokwr"}],"gsx$address":{"$t":"303 Tarpon Trace \u003cbr\u003eByron, GA 31008"},"gsx$mls":{"$t":"122445"},"gsx$state":{"$t":"GA"},"gsx$county":{"$t":"Bibb County"},"gsx$city":{"$t":"Macon"},"gsx$subdivision":{"$t":"None"},"gsx$high":{"$t":"Northside"},"gsx$beds":{"$t":"4"},"gsx$baths":{"$t":"3"},"gsx$price":{"$t":"250000"},"gsx$cars":{"$t":"3"}},{"id":{"$t":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/cpzh4"},"updated":{"$t":"2015-01-08T04:27:29.693Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"104 Gretta Court \u003cbr\u003eWarner Robins, GA 31088"},"content":{"type":"text","$t":"mls: 122444, state: GA, county: Bibb County, city: Macon, subdivision: None, high: Bibb-Westside, beds: 3, baths: 2, price: 200000, cars: 2"},"link":[{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/cpzh4"}],"gsx$address":{"$t":"104 Gretta Court \u003cbr\u003eWarner Robins, GA 31088"},"gsx$mls":{"$t":"122444"},"gsx$state":{"$t":"GA"},"gsx$county":{"$t":"Bibb County"},"gsx$city":{"$t":"Macon"},"gsx$subdivision":{"$t":"None"},"gsx$high":{"$t":"Bibb-Westside"},"gsx$beds":{"$t":"3"},"gsx$baths":{"$t":"2"},"gsx$price":{"$t":"200000"},"gsx$cars":{"$t":"2"}},{"id":{"$t":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/cre1l"},"updated":{"$t":"2015-01-08T04:27:29.693Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"112 Derringer Court \u003cbr\u003eByron, GA 31008"},"content":{"type":"text","$t":"mls: 120081, state: GA, county: Bibb County, city: Macon, subdivision: Woolfolk, high: See Remarks, beds: 2, baths: 2, price: 150000, cars: 1"},"link":[{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/cre1l"}],"gsx$address":{"$t":"112 Derringer Court \u003cbr\u003eByron, GA 31008"},"gsx$mls":{"$t":"120081"},"gsx$state":{"$t":"GA"},"gsx$county":{"$t":"Bibb County"},"gsx$city":{"$t":"Macon"},"gsx$subdivision":{"$t":"Woolfolk"},"gsx$high":{"$t":"See Remarks"},"gsx$beds":{"$t":"2"},"gsx$baths":{"$t":"2"},"gsx$price":{"$t":"150000"},"gsx$cars":{"$t":"1"}},{"id":{"$t":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/chk2m"},"updated":{"$t":"2015-01-08T04:27:29.693Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"105 Kennedy Court \u003cbr\u003eWarner Robins, GA 31093"},"content":{"type":"text","$t":"mls: 116141, state: GA, county: Macon County, city: Oglethorpe, subdivision: See Remarks, high: See Remarks, beds: 1, baths: 1, price: 50000, cars: 1"},"link":[{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/1z7X-IvfauqyWul4oh_46e471nNt13ZpNhyXSIm_NXKI/ow6ewk2/public/values/chk2m"}],"gsx$address":{"$t":"105 Kennedy Court \u003cbr\u003eWarner Robins, GA 31093"},"gsx$mls":{"$t":"116141"},"gsx$state":{"$t":"GA"},"gsx$county":{"$t":"Macon County"},"gsx$city":{"$t":"Oglethorpe"},"gsx$subdivision":{"$t":"See Remarks"},"gsx$high":{"$t":"See Remarks"},"gsx$beds":{"$t":"1"},"gsx$baths":{"$t":"1"},"gsx$price":{"$t":"50000"},"gsx$cars":{"$t":"1"}}]}});
I want to sort the information by several JSON entries (beds, baths, etc.). I have tried dozens of things but have yet to get a result. I even tried sorting the data at the spreadsheet with a query, but I don't think I could get a working JSON response back.
For the sort, I'm going to use Lodash. I'll also post the same solution in pure JS for those people that want it. The first thing we need to focus on is getting a list of the keys in order. So, let's start with a test object. I'm just gonna throw in some random fields/types:
var obj= {
test : 5,
colour : 'red',
song : 'I Believe In A Thing Called Love',
profession : 'Singer',
gender : 'Male',
languages : {
array : [ 'French', 'German', 'English' ]
},
relationships : {
'sister' : 'Jasmine',
'brother' : 'Ryan'
}
}
The first thing we need to do is gain access to a list of the keys inside the object. Fortunately for us, this is pretty simple to do:
// Underscore
var keys = _.keys(object);
// → ['test', 'colour', 'song', 'profession', 'gender', 'languages', 'relationships']
// JavaScript
var keys = Object.keys(object);
// → ['test', 'colour', 'song', 'profession', 'gender', 'languages', 'relationships']
The first thing you'll notice is that this only gives us top-level keys. This is due to the fact that the inner objects are technically different objects. Were we to call either of the above on the inner object, we would get their keys too.
Ok, so we now have a list of top-level keys in the object. Naturally, the next step is to sort these keys alphabetically, which is also pretty simple. Underscore has a nice method already available for this which makes it pretty easy, but it's also very casual to implement in pure JS.
// Underscore
var sortedKeys = _.sortBy(keys, function(key){
return object[key];
});
// JavaScript
var sortedKeys = keys.sort(function(key1, key2){
var val1 = object[key1].toLowerCase();
var val2 = object[key2].toLowerCase();
if(val1 < val2 ) return -1;
if(val1 > val2 ) return 1;
return 0;
})
// Both → ['colour', 'gender', 'languages', 'profession', 'relationships', 'song', 'test']
And finally:
var sortedObj = {};
// Underscore
_.each(keys, function(key) {
sortedObj[key] = object[key];
});
// JavaScript
for(var index in keys){
var key = keys[index];
sortedObj[key] = object[key];
}
// Resulting object
{
'colour': 'red',
'gender': 'Male',
'languages': {
'array': [
'French',
'German',
'English'
]
},
'profession': 'Singer',
'relationships': {
'sister': 'Jasmine',
'brother': 'Ryan'
},
'song': 'I Believe In A Thing Called Love',
'test': 5
}
If needed for inner objects as well, we just put in a check for an object type and then recall the code if it's an object:
if(typeof object[key] == 'object'){
sortedObj[key] = sortObject(object[key]); // sortObj will be the function holding this code
} else {
sortedObj[key] = object[key];
}
Source