GroupBy and reduce an array of object in a pointfree style - javascript

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

Related

How to get max and min value from Array in Javascript?

i had seen lots of another examples like
Math.max(...Array1) or Math.max(null,num) or Math.max.apply(null,num)
but it's not working by my code
my data size is 255 and This is what the data looks like when i print it by console.log
0: 55.47999954223633
1: 56.040000915527344
2: 57.52000045776367
3: 57.119998931884766
...
Data was extracted from the json file and then put into the array through push.
code is look like this
let Array =[]
jQuery.getJSON( "price.json",function(data){
for(let i=0;i<data.length;i++){
Array.push(data[i].price)
}
let maximum = Math.max(...Array) // not working
Thank you for reading this.
Math.max(...[]) is ES6 syntax. Maybe you are using an older JavaScript engine? Here are two versions using your data as input, one for newer ES6, one for older ES5:
const dataFromJson = [
{ name: "A", price: 55.47999954223633 },
{ name: "A", price: 56.040000915527344 },
{ name: "A", price: 57.52000045776367 },
{ name: "A", price: 57.119998931884766 }
];
// ES6:
let arr1 = dataFromJson.map(obj => obj.price);
let max1 = Math.max(...arr1);
console.log('ES6 max: ' + max1);
// ES5:
let arr2 = dataFromJson.map(function(obj) {
return obj.price;
});
let max2 = Math.max.apply(null, arr2);
console.log('ES5 max: ' + max2);
Output:
ES6 max: 57.52000045776367
ES5 max: 57.52000045776367

Looking to form 2 sets of data from a object of arrays - Javascript

I am looking for help with the data in a football related app I am building.
Take this as a sample of the object I am dealing with:
const squad = {
goalkeepers: [
{ player1: { score: 10 } },
{ player2: { score: 12 } }
],
defenders: [
{ player3: { score: 3 } },
{ player4: { score: 19 } },
{ player5: { score: 5 } },
{ player6: { score: 21 } },
{ player7: { score: 6 } },
],
midfielders: [
{ player8: { score: 7 } },
{ player9: { score: 1 } },
{ player10: { score: 18 } },
{ player11: { score: 11 } },
{ player12: { score: 8 } },
],
attackers: [
{ player13: { score: 7 } },
{ player14: { score: 2 } },
{ player15: { score: 16 } }
]
}
There are 15 players here and I want to divide them into two groups:
A strongest possible outfield team of 11 players based on their score value.
The remaining 4 players are to go on the bench.
The twist here is that there is a minimum and maximum number of players required in each position of the 11 outfield players.
Goalkeepers: EXACTLY 1.
Defenders: MIN 3, MAX 5.
Midfielders: MIN 2, MAX 5.
Forwards: MIN 1, MAX 3.
For anyone familiar with Fantasy Premier League, the rules work the same way:
Your team can play in any formation providing that 1 goalkeeper, at least 3 defenders and at least 1 forward are selected at all times.
I've tried concatinating the arrays to one large array and sorting them by player score value but I can't work out how to calculate the strongest first 11 players from that point while adhering to the position rules.
Any help would be greatly appreciated.
I find a modular approach more intuitive and resilient.
This solution breaks the problem down into first converting your data to a more useful format, then finding all the combination of eleven players, then filtering out those that don't match the rules, then choosing the one with the largest score:
// utility functions
const maximumBy = (fn) => (xs) =>
xs .reduce ((a, x, i) => fn (x) > fn (a) ? x : a, xs [0] || null)
const choose = (n, xs) =>
n < 1 || n > xs .length
? []
: n == 1
? [...xs .map (x => [x])]
: [
...choose (n - 1, xs .slice (1)) .map (ys => [xs [0], ...ys]),
...choose (n , xs .slice (1))
]
// helper functions
const simplify = (squad) =>
Object .entries (squad) .flatMap (
([position, vs]) => vs .flatMap (
(v) => Object.entries (v) .flatMap (([name, s]) => ({position, name, ...s}))
)
)
const validate = (rules) => (lineup) => rules .every (({position, min, max}) => {
const count = lineup .filter (({position: p}) => p == position) .length
return count >= min && count <= max
})
const totalScore = (lineup) =>
lineup .reduce ((t, {score}) => t + score, 0)
// main function
const bestLineup = (squad, rules) =>
maximumBy (totalScore) (choose (11, simplify (squad)) .filter (validate (rules)))
// sample data
const rules = [{position: 'goalkeepers', min: 1, max: 1}, {position: 'defenders', min: 3, max: 5}, {position: 'midfielders', min: 2, max: 5}, {position: 'attackers', min: 1, max: 3}]
const squad = {goalkeepers: [{player1: {score: 10}}, {player2: {score: 12}}], defenders: [{player3: {score: 3}}, {player4: {score: 19}}, {player5: {score: 5}}, {player6: {score: 21}}, {player7: {score: 6}}], midfielders: [{player8: {score: 7}}, {player9: {score: 1}}, {player10: {score: 18}}, {player11: {score: 11}}, {player12: {score: 8}}], attackers: [{player13: {score: 7}}, {player14: {score: 2}}, {player15: {score: 16}}]}
// demo
console .log (bestLineup (squad, rules))
.as-console-wrapper {max-height: 100% !important; top: 0}
We start with two general-purpose utility functions we might use in other projects
maximumBy takes a function that converts a value into a comparable one -- in this problem we use it to extract the score -- and return a function that accepts an array of values, runs that function on each and chooses the one with the largest score. (This version is less efficient than it could be. I'd rather focus on simplicity at the moment.
choose finds all subsets of n elements of your array of values. For instance, choose (2, ['a', 'b', 'c', 'd']) returns [['a', 'b'], ['a', 'c'], ['a', 'd']. ['b', 'c'], ['b', 'd'], ['c', 'd']].
Then we have some helper functions,
simplify turns your initial squad format into something more tractable:
[
{name: "player1", position: "goalkeepers", score: 10},
{name: "player2", position: "goalkeepers", score: 12},
{name: "player3", position: "defenders", score: 3},
// ...
{name: "player15", position: "attackers", score: 16}
]
validate takes an array of rules such as
{position: 'defenders', min: 3, max: 5}
and returns a function that takes a lineup from the squad and reports whether that lineup obeys all the rules.
totalScore takes a lineup and sums up the scores of all the players
Finally, our main function, bestLineup accepts a squad and an array of rules, simplifies the squad, chooses all lineups of eleven players, filters it down to just those which validate according to the rules, and chooses the maximum by our function that calculates their total score.
If you want the output in the same format as the input, we can just call one more helper to undo our simplify; let's call it complexify:
const complexify = (xs) =>
xs .reduce (
(a, {name, position, score}) => (
(a [position] = a [position] || []), (a [position] .push ({[name]: {score}})), a
), {})
which will transform an array of the simple format used above back into something like this:
{
goalkeepers: [
{player2: {score: 12}}
],
defenders: [
{player4: {score: 19}},
{player5: {score: 5}},
{player6: {score: 21}},
{player7: {score: 6}}
],
midfielders: [
{player8: {score: 7}},
{player10: {score: 18}},
{player11: {score: 11}},
{player12: {score: 8}}
]
attackers: [
{player13: {score: 7}},
{player15: {score: 16}}
],
}
But I would only do this if your unusual data format is forced upon you.

How to filter data of object according to keys and make new array in react?

I am working with the object like this:-
{
0:
buyAmount: 16.664328043964396
buyAmountInUsd: 16.685266204095775
date: {date: '2021-12-07'}
sellAmount: {USD: 500, INR: 35000}
1:
buyAmount: 1004.7015442959262
buyAmountInUsd: 1005.9639175379324
date: {date: '2021-12-07'}
sellAmount: {USD: 1000, INR: 79000}
......
and I am trying to make a new array using useState hook with this data but the problem I am facing is how to filter data and make an almost similar array with the same data.
e.g.:-
0: [amount: 500, date: '2021-12-07'],
1: [amount: 1000, date: '2021-12-07']
The problem I am facing is I don't know the approach how to get the data like amount = sellAmount.USD and date = date.date
I thought of trying the for...of But I don't think it will be a good hit.
You can do this with Array.map
const arr = [{
buyAmount: 16.664328043964396,
buyAmountInUsd: 16.685266204095775,
date: {date: '2021-12-07'},
sellAmount: {USD: 500, INR: 35000}
},{
buyAmount: 1004.7015442959262,
buyAmountInUsd: 1005.9639175379324,
date: {date: '2021-12-07'},
sellAmount: {USD: 1000, INR: 79000}
}]
console.log(
arr.map(initialValue => {
return {
amount: initialValue.sellAmount.USD,
date: initialValue.date.date
}
})
)
The better idea would be the having an array of objects rather than array of array elements
let result = yourArray.map(each => ({ amount: each.sellAmount.USD, date: each.date.date }))
This is not related to ReactJS - just native Javascript object handling.
If your original data has the shape of an Object you can convert it into an Array like so:
const objData = { 0: {...}, 1: {...} };
const arrData = Object.entries(objData); // [[0, {...}], [1, {...}]];
From there, you can filter/map/sort your array with native array methods:
const reshapedArray = arrData.map(([key, value]) => {
return {
amount: value.sellAmount.USD,
date: value.date.date,
};
});
Then sort:
const sortedArray = reshapedArray.sort((prev, next) => {
return prev.amount - next.amount; // sort by amount ascending
});
You can of could chain these array functions and shorten the syntax a bit:
Object.entries(objData)
.map(([_, { sellAmount, date: { date } }]) => ({ amount: sellAmount.USD, date }))
.sort((a, b) => a.amount - b.amount);

JSON manipulation - javascript - adding new keys and shifting data

I'm learning to manipulate JSON data and I am stuck trying to figure out how to cajole the following JSON into what I want as shown below:
Any pointers to function/terms/concepts that I should learn for this sort of problem would be greatly appreciated! Thanks
JSON object
{
car: 1,
van: 5,
cat: 99999999999999999999999
}
Desired outcome:
items: [
{ "type": "car", "value": "1"},
{ "type": "van", "value": "5"},
{ "type": "cat", "value": "99999999999999999999999"}
]
You can use a combination of Object.entries and Array.prototype.map:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj) // [["car",1],["van",5],["cat",99999999999999999999999]]
.map(x => ({ type: x[0], value: x[1] }));
console.log(list);
Or, with some destructuring:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj)
.map(([type, value]) => ({ type, value }));
console.log(list);
The callback to map:
([type, value]) => ({ type, value })
Expects an array as parameter: [type, value]. The first value in that array is assigned to type, the second one to value.
Then we use a shorthand form to set these values in our returned object:
=> ({ type, value })
I'm a beginner. I tried to solve the problem and this is the best I can come up with, tested in Node.js 10.
const obj = {"car": 1, "van": 5, "cat": 999999}
const items = []
for (let key in obj) {
items.push({"type": key, "value": obj[key]})
}
console.log(items)
One thing I am slightly confused about is the difference between for..in vs for..of, I'm currently looking into it.
Object.keys will return:
['car', 'van', 'cat'];
On this array you can use Array's map function which creates a new array with the results of calling a provided function on every element in the calling array.
var a = {
car: 1,
van: 5,
cat: 99999999999999999999999
}
m = Object.keys(a).map((v)=>{
return {
type: v,
value: a[v]
}
})
console.log(m);
#GustavMahler hope you understand. To learn more about array functions you should look map, reduce and filter.
This one uses object.keys
let js = {car:1, van:5, cat:9999}
Object.keys(js).map( x => ({type: x, value: js[x] }) )
[ { type: 'car', value: 1 },
{ type: 'van', value: 5 },
{ type: 'cat', value: 9999 } ]

prevent duplication code with ramda

i have a list of same operation on same list using ramda like below :
size: { sum: R.sum(R.map(R.prop('size'), ordersRep)) },
price: { sum: R.sum(R.map(R.prop('price'), ordersRep)) },
profit: { sum: R.sum(R.map(R.prop('profit'), ordersRep)) },
discount: { sum: R.sum(R.map(R.prop('discount'), ordersRep)) },
which i want to define main function : R.sum(R.map(R.prop('somthing'), ordersRep)) other place and use it wheneve needed. but it's take two argument one list and one prop name. how can i handle it ?
let combined = (prop, coll) => R.sum(R.pluck(prop, coll))
For an arguably more functional version (point-free courtesy of Ross Mackay):
let combined = prop => R.compose(R.sum, R.pluck(prop))
let sumPrice = combined('price');
sumPrice([{price: 2}, {price: 3}]); // 5
Point-free:
let combined = R.curryN(2, R.compose(R.sum, R.pluck));

Categories