prevent duplication code with ramda - javascript

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));

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

Is there a better way to sum up values of a nested array?

I currently have the following items and implementation below. I am not sure if using reduce within another reduce is performant. Is there a better way to sum up nested arrays?
const items = [
{
id: 111,
fruits: [
{
name: "apple",
total: 5
},
{
name: "pineapple",
total: 1
}
]
},
{
id: 222,
fruits: [
{
name: "kiwi",
total: 2
}
]
}
];
// my implementation using reduce within another to get the sum of totals.
const sumOfFruits = items
.reduce((sum, curr) => sum + curr.fruits
.reduce((fruitSum, fruitCurr) => fruitSum + fruitCurr.total));
console.log(sumOfFruits);
// returns 8
A cleaner (not necessarily faster) way to do this would be:
collect all "fruits" (flatMap)
pick "totals" (map)
sum the result
const items = [
{
id: 111,
fruits: [
{
name: "apple",
total: 5
},
{
name: "pineapple",
total: 1
}
]
},
{
id: 222,
fruits: [
{
name: "kiwi",
total: 2
}
]
}
];
//
res = items
.flatMap(x => x.fruits)
.map(x => x.total)
.reduce((s, n) => s + n, 0)
console.log(res)
Regarding performance, the thing about javascript is that it's performant enough unless you have millions of objects. And if you do have millions of objects, you shouldn't be using javascript in the first place.
Your code does not produce the desired output: it coerces an object to string and performs string concatenation.
This is because reduce is called without second argument, and thus the accumulator gets the first object as value, while you want the accumulated value to be a number.
So you need to add 0 as second argument for the outer reduce call. For the inner reduce call you can improve a little bit and provide sum as initial value. That way you don't have to do sum + anymore.
You can also make use of destructuring in the callback parameters:
This leads to the following code:
const items = [{id: 111,fruits: [{name: "apple",total: 5},{name: "pineapple",total: 1}]},{id: 222,fruits: [{name: "kiwi",total: 2}]}];
const sumOfFruits = items.reduce(
(sum, {fruits}) => fruits.reduce(
(fruitSum, {total}) => fruitSum + total,
sum // Continue with the sum we already have
), 0 // Start with 0 for the accumulator
);
console.log(sumOfFruits); // 8
Many would agree that this is how it should be done. If performance really is an issue, then you can get a slight improvement by using plain old for loops. These do not use callbacks, and so can be expected to do the job a little bit faster, but with less elegant code. In my experience they also perform a tiny bit faster than for..of loops:
var items = [{id: 111,fruits: [{name: "apple",total: 5},{name: "pineapple",total: 1}]},{id: 222,fruits: [{name: "kiwi",total: 2}]}];
var sumOfFruits = 0;
for (var i = 0; i < items.length; i++) {
var fruits = items[i].fruits;
for (var j = 0; j < fruits.length; j++) {
sumOfFruits += fruits[j].total;
}
}
console.log(sumOfFruits); // 8
It probably is not worth the millisecond you would gain from this with normal sized input.

How to reformat a JSON array into another format "grouping" based on different keys

Question: How can I reformat this JSON array by "grouping" via different keys, using ReactJS?
I have a JSON array as :
[
{Product: "Shoes", Sold: 5, Bought : 0, Reversed : 2} ,
{Product: "Table", Sold: 2, Bought : 0, Reserved : 4}
]
The reason for this is the data type I'm working with, and on realizing I need to visualize this data in a different way (due to one of the graph packages I am using) I need to structure this data as:
[
{
Status: "Sold",
Shoes : 5,
Table : 2
} ,
{
Status: "Bought",
Shoes : 0,
Table : 0
} ,
{
Status: "Reserved",
Shoes : 2,
Table : 4
}
]
So I'm grouping the data into the keys other than Product, and then the keys after this are Product with the Value being the Product and it's "status".
Frankly, I am at a complete loss as to what to do, as I'm thinking the code required to generate this would be quite convoluted, so I'm very open to know if this just is too much work.
const data = [
{
Product: "Shoes",
Sold: 5,
Bought : 0,
Reserved : 2
} , {
Product: "Table",
Sold: 2,
Bought : 0,
Reserved : 4
}
];
let resultData = [];
Object.keys(data[0]).forEach((key, idx) => {
if (idx !== 0) {
let resultUnit = {
Status: key,
};
data.forEach(item => {
return resultUnit = {
...resultUnit,
[item.Product]: item[key],
}
})
resultData.push(resultUnit);
}
})
console.log(resultData);
// 0: {Status: "Sold", Shoes: 5, Table: 2}
// 1: {Status: "Bought", Shoes: 0, Table: 0}
// 2: {Status: "Reserved", Shoes: 2, Table: 4}
You can do this using the Array.reduce function. (Actually, two reduce functions).
Here's an extensible solution that allows for other statuses.
Note that I changed everything to lowercase, as is standard convention.
const items = [
{product: "Shoes", sold: 5, bought : 0, reserved : 2} ,
{product: "Table", sold: 2, bought : 0, reserved : 4}
]
//We declare the status types here.
const keys = ["sold", "bought", "reserved"];
// Just create the initial 'statuses' array.
function initAcc(keys) {
return keys.map((key) => {
return {
status: key
}
});
}
//Here we are iterating over each item, getting it to return a single accumulator array each time.
const newItems = items.reduce((acc, cur) => {
return addItemToAccumulator(acc, cur);
}, initAcc(keys));
console.log(newItems);
// This function maps of the accumulator array (ie. over each status).
function addItemToAccumulator(acc, item) {
return acc.reduce((acc, statusLine) => {
//Find the count from the existing status if it exists,
//Add the current items count for that status to it.
const itemCount = item[statusLine.status] + (statusLine[item.product] || 0);
//Return a modified status, with the new count for that product
return [
...acc,
{
...statusLine,
[item.product]: itemCount
}
];
}, []);
}
Lets just do a simple loop function and create a couple objects to clearly solve the problem here:
const data = [YOUR_INITIAL_ARRAY];
let Sold, Bought, Reserved = {};
data.forEach(({Product, Sold, Bought, Reserved})=> {
Sold[Product] = Sold;
Bought[Product] = Bought;
Reservered[Product] = Reserved;
});
let newArray = [Sold, Bought, Reserved];
I think you can see where this is going ^ I see a few others have given complete answers, but try and go for the clear understandable route so it makes sense.
All you have to do after this is set the status which i'd do off an enum and you are good

GroupBy and reduce an array of object in a pointfree style

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

Inserting dynamic value into array of objects, javascript

I have an array of objects that look something like this;
[
{Number: 5002000, Origin: 123456, Count: 128},
{Number: 5002300, Origin: 900231, Count: 52},
{Number: 5002022, Origin: 534323, Count: 269}
]
Now I'm trying to multiply the "Count" value with a value from a designated price pool.
Which looks something like this;
[
{Prefix: 50023, Price: 20},
{Prefix: 50020, Price: 10},
{Prefix: 5002, Price: 60},
]
Currently there's an horrendous for loop with if-statements.
for (var key in sData) {
if (sData[key].Origin.startsWith('50023')) {
sData[key].sum = (sData[key].Count * 20);
}
else if (sData[key].Origin.startsWith('50020')) {
sData[key].sum = (sData[key].Count * 10);
}
// continues...
}
startsWith is a function that simply checks if the value starts with the (value).
Is there already a function in JS to map two arrays of objects? (I'm also having issues with the logic since the "Prefix" value basically has to go from the top down as not to land on the default "5002"-prefix.)
You should use nested loops in this situation. Also switch to Array.forEach method.
sData.forEach(function(item_, key) {
prices.forEach(function(item) {
if (sData[key].Origin.startsWith(item.Prefix)) {
sData[key].sum = (sData[key].Count * item.Price);
}
});
})
Assuming that second array can be transformed into the hash:
var tiers = {
50023: {Prefix: 50023, Price: 20},
50020: {Prefix: 50020, Price: 10},
5002: {Prefix: 5002, Price: 60},
}
You may make it look like this:
for (var key in sData) {
var key = String(sData[key])
var keyIndex = key.slice(5)
if (tiers.hasOwnProperty(keyIndex)) {
var price = tiers[keyIndex].Price
sData[key].sum = (sData[key].Count * price)
} else {
// Fallback solution
}
}
Going further you may even think of some recursive solution for fallback.

Categories