Using ramda to modify data in array - ramda.js

Input:
[
{
temp: "24",
date: "2019-10-16T11:00:00.000Z"
}
]
Output:
[[new date("2019-10-16T11:00:00.000Z").getTime(), 24]]
Got some annoying mutability problems if I do it in vanilla javascript.
Good case to use ramda.
Something like:
const convertFunc = ...
const convertArr = R.map(convertFunc)
const result = convertArr(arr);
I'm stuck. Any ideas what Ramda functions to use?

I'm not sure Ramda would add anything substantial. Especially if you can use parameter destructuring:
map(({temp, date}) => [new Date(date).getTime(), temp],
[{ temp: "24",
date: "2019-10-16T11:00:00.000Z"}]);
//=> [[1571223600000, "24"]]

You can map the array of objects, and use R.evolve to convert the date string to time via Date.parse(), and then get the R.props to convert to an array of arrays.
const { map, pipe, evolve, identity, props } = R
const fn = map(pipe(
evolve({ temp: identity, date: Date.parse }),
props(['date', 'temp'])
))
const data = [{temp: "24",date: "2019-10-16T11:00:00.000Z"}]
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Related

Ramda - get ids from nested array of multiple objects

I am trying to get outgoingNodes IDs which are stored in array which is inside objects like in example below but I have no idea where to start...:
const nodes = {
"818": {
"id": "818",
"index": 1,
"outgoingNodes": [
"819"
],
},
"819": {
"id": "819",
"outgoingNodes": [
"820",
"821"
],
}
}
I would like to get an array of IDs as a result. Any help will be appreciated.
Get the values (sub objects), pluck the outgoingNodes arrays, and flatten to a single array:
const { pipe, values, pluck, flatten } = R
const fn = pipe(
values,
pluck('outgoingNodes'),
flatten
)
const nodes = {"818":{"id":"818","index":1,"outgoingNodes":["819"]},"819":{"id":"819","outgoingNodes":["820","821"]}}
const result = fn(nodes)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
Another option is to combine getting the outgoingNodes arrays, and flattening to a single array using R.chain with R.prop:
const { pipe, values, chain, prop } = R
const fn = pipe(
values,
chain(prop('outgoingNodes')),
)
const nodes = {"818":{"id":"818","index":1,"outgoingNodes":["819"]},"819":{"id":"819","outgoingNodes":["820","821"]}}
const result = fn(nodes)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

Ramda.js - How to make R.merge point free - 2 functions with same dataset

Given the below code snippet that uses Ramda.js, are there multiple ways of making parseDetails point free? If so, is there an "ideal" way of doing it?
const someData = {
products: [{
stockId: 123,
name: "chocolate",
translatedTags: {
wasPrice: "Was 10"
}
}]
}
const getProductData = R.pipe(
R.pathOr([], ['products', 0]),
R.pick([
'stockId',
'name']
)
);
const getProductTags = R.pipe(
R.pathOr([], ['products', 0, 'translatedTags'])
);
const parseDetails = (data) => R.merge(
getProductData(data),
getProductTags(data)
);
parseDetails(someData);
There are several ways I can think of, but by far the best is to use lift. The way I think of using lift is that it takes a function which works on values and returns an equivalent function which works on containers of those values.
And functions that return those values can be thought of as containers. So we can do it like this:
const getProductData = pipe (
pathOr ([], ['products', 0]),
pick ([
'stockId',
'name']
)
)
// `pipe` not necessary here
const getProductTags = pathOr ([], ['products', 0, 'translatedTags'])
const parseDetails = lift (merge) (getProductData, getProductTags)
const someData = {products: [{stockId: 123, name: "chocolate", translatedTags: {wasPrice: "Was 10"}}]}
console .log (parseDetails (someData))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {pipe, pathOr, pick, lift, merge} = R </script>
And if you have no other need for getProductData or getProductTag, you can inline them in the function:
const parseDetails = lift (merge) (
pipe (pathOr ([], ['products', 0]), pick (['stockId', 'name'])),
pathOr ([], ['products', 0, 'translatedTags'])
)

Lodash: filter when I have nested array property?

Consider below example. I am using Lodash
"home":[
{
"data":{
"interests":["sports", "travel", "boxing"],
"city":["x", "y", "z"],
"name":"test1"
},
"count":["1", "2"],
"country":"CA"
},
{
"data":{
"interests":["painting", "travel", "dancing"],
"city":["a", "y", "b"],
"name":"test2"
},
"count":["1","3"],
"country":"US"
}
]
If I'll try the function on key value pair example :
_.find(home, ['data.country', 'US']); // It is returning me the 2nd object
requirement :
I want all the objects where data.interests is 'dancing'.
Tried :
_.find(home, ['data.interests', 'dancing']) // It is returning []
I have also tried filter(), where() and map but unable to get the complete object.
Thanks in advance.
You can use vanilla JS or lodash funcntions - Filter the array, and for each item check if the data.interests array includes the requested word.
Vanilla:
const home = [{"data":{"interests":["sports","travel","boxing"],"city":["x","y","z"],"name":"test1"},"count":["1","2"],"country":"CA"},{"data":{"interests":["painting","travel","dancing"],"city":["a","y","b"],"name":"test2"},"count":["1","3"],"country":"US"}]
const result = home.filter(o => o.data.interests.includes('dancing'))
console.log(result)
Lodash:
const home = [{"data":{"interests":["sports","travel","boxing"],"city":["x","y","z"],"name":"test1"},"count":["1","2"],"country":"CA"},{"data":{"interests":["painting","travel","dancing"],"city":["a","y","b"],"name":"test2"},"count":["1","3"],"country":"US"}]
const result = _.filter(home, o => _.includes(o.data.interests, 'dancing'))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

Replace value of key using ramda.js

I have the following array of objects:
const originalArray = [
{name: 'name1', value: 10},
{name: 'name2', value: 20}
]
And the following object
names = {
name1: 'generic_name_1',
name2: 'generic_name_2'
}
I would like the first array to be transformed like this:
[
{name: 'generic_name_1', value: 10},
{name: 'generic_name_2', value: 20}
]
What I have tried so far:
const replaceName = (names, obj) => {
if(obj['name'] in names){
obj['name'] = names[obj['name']];
}
return obj;
}
const modifiedArray = R.map(replaceName(names), originalArray)
Is there a more ramda-ish way to do this?
Using native JS inside Ramda functions is not unramdaish. The only problem in your code is that you mutate the original object - obj['name'] = names[obj['name']];.
I would use R.when to check if the name exists in the names object, and if it does evolve the object to the new name. If it doesn't the original object would be returned.
const { flip, has, prop, map, when, pipe, evolve } = R
const hasProp = flip(has)
const getProp = flip(prop)
const fn = names => map(when(
pipe(prop('name'), hasProp(names)),
evolve({
name: getProp(names)
})
))
const originalArray = [{"name":"name1","value":10},{"name":"name2","value":20},{"name":"name3","value":30}]
const names = {"name1":"generic_name_1","name2":"generic_name_2"}
const result = fn(names)(originalArray)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
I wouldn't use any Ramda functions for this. I would simply avoid mutating the original, perhaps with code like this:
const transform = (names) => (arr) => arr .map (
({name, ... rest}) => ({name: names [name] || name, ... rest})
)
const originalArray = [{name: 'name1', value: 10},{name: 'name2', value: 20}]
const names = {name1: 'generic_name_1',name2: 'generic_name_2'}
console .log (
transform (names) (originalArray)
)

Ramda js maximum elements

I wonder how will be the best way to get max elements from array.
For example I have regions with temperaturs:
let regions = [{name: 'alabama', temp: 20}, {name: 'newyork', temp: 30}...];
It can be done with one line but I want to be performant.
I want to iterate over the array only once.
If more than 1 region has the same max temperature i want to get them all
Do you know a way to make it with more compact code than procedure code with temporary variables and so on.
If it can be done in "functional programming" way it will be very good.
This is sample procedure code:
regions = [{name:'asd', temp: 13},{name: 'fdg', temp: 30}, {name: 'asdsd', temp: 30}]
maxes = []
max = 0
for (let reg of regions) {
if (reg.temp > max) {
maxes = [reg];
max = reg.temp
} else if (reg.temp == max) {
maxes.push(reg)
} else {
maxes =[]
}
}
Another Ramda approach:
const {reduce, append} = R
const regions = [{name:'asd', temp: 13},{name: 'fdg', temp: 30}, {name: 'asdsd', temp: 30}]
const maxTemps = reduce(
(tops, curr) => curr.temp > tops[0].temp ? [curr] : curr.temp === tops[0].temp ? append(curr, tops) : tops,
[{temp: -Infinity}]
)
console.log(maxTemps(regions))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
This version only iterates the list once. But it's a bit ugly.
I would usually prefer the version from Ori Drori unless testing shows that the performance is a problem in my application. Even with the fix from my comment, I think that code is easier to understand than this one. (That wouldn't be true if there were only two cases. (< versus >= for instance.) But when there are three, this gets hard to read, however we might format it.
But if performance is really a major issue, then your original code is probably faster than this one too.
Use R.pipe to
Group the objects by temp's value,
Convert the object of groups to an array of pairs
Reduce the pairs to the one with the max key (the temp)
return the value from the pair
const { pipe, groupBy, prop, toPairs, reduce, maxBy, head, last } = R;
const regions = [
{name: 'california', temp: 30},
{name: 'alabama', temp: 20},
{name: 'newyork', temp: 30}
];
const result = pipe(
groupBy(prop('temp')),
toPairs,
reduce(maxBy(pipe(head, Number)), [-Infinity]),
last
)(regions);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
A different approach to this (albeit a little more verbose) is to create some helpers to generically take care of folding over a list of things to extract the list of maximums.
We can do this by defining a Semigroup wrapper class (could also be a plain function instead of a class).
const MaxManyBy = fn => class MaxMany {
constructor(values) {
this.values = values
}
concat(other) {
const otherValue = fn(other.values[0]),
thisValue = fn(this.values[0])
return otherValue > thisValue ? other
: otherValue < thisValue ? this
: new MaxMany(this.values.concat(other.values))
}
static of(x) {
return new MaxMany([x])
}
}
The main purpose of this class is to be able to combine two lists by comparing the values contained within, with the invariant that each list contains the same comparable values.
We now can introduce a new helper function which applies some function to each value of a list and then combines them all using concat.
const foldMap = (fn, [x, ...xs]) =>
xs.reduce((acc, next) => acc.concat(fn(next)), fn(x))
With these helpers, we can now create a function that pulls the maximum temperatures from your example.
const maxTemps = xs =>
foldMap(MaxManyBy(({temp}) => temp).of, xs).values
maxTemps([
{name: 'california', temp: 30},
{name: 'alabama', temp: 20},
{name: 'newyork', temp: 30}
])
//=> [{"name": "california", "temp": 30}, {"name": "newyork", "temp": 30}]
There is an assumption here that the list being passed to foldMap is non-empty. If there's a chance that you'll encounter an empty list then you will need to modify accordingly to return a default value of some kind (or wrap it in a Maybe type if no sane default exists).
See the complete snippet below.
const MaxManyBy = fn => class MaxMany {
constructor(values) {
this.values = values
}
concat(other) {
const otherValue = fn(other.values[0]),
thisValue = fn(this.values[0])
return otherValue > thisValue ? other
: otherValue < thisValue ? this
: new MaxMany(this.values.concat(other.values))
}
static of(x) {
return new MaxMany([x])
}
}
const foldMap = (fn, [x, ...xs]) =>
xs.reduce((acc, next) => acc.concat(fn(next)), fn(x))
const maxTemps = xs =>
foldMap(MaxManyBy(({temp}) => temp).of, xs).values
const regions = [
{name: 'california', temp: 30},
{name: 'alabama', temp: 20},
{name: 'newyork', temp: 30}
]
console.log(maxTemps(regions))