Ramda - how to use multiple functions on the same data structure - ramda.js

I am trying to use multiple ramda functions on this example:
const data = {
"tableItems": [
{
"id": 1,
"name": "1",
"startingPoint": true,
"pageNumber": 15,
"nodes": [
100,
200
]
},
{
"id": 2,
"name": "2",
"startingPoint": true,
"pageNumber": 14,
"nodes": [
300,
400
]
}
],
"nodes": [
{
"id": 100,
"tableItemId": 1,
"content": "test"
},
{
"id": 200,
"tableItemId": 1,
"content": "test"
},
{
"id": 300,
"tableItemId": 2,
"content": "test"
},
{
"id": 400,
"tableItemId": 2,
"content": "test"
}
]
}
I am trying to create new JSON which should look like this where nodes array should be filled with another ramda function:
const newJSON = [
{
"id": "chapter-1",
"name": "2",
"nodes": []
},
{
"id": "chapter-2",
"name": "1",
"nodes": []
}
]
I started with:
let chapters = [];
let chapter;
const getChapters = R.pipe(
R.path(['tableItems']),
R.sortBy(R.prop('pageNumber')),
R.map((tableItem) => {
if(tableItem.startingPoint) {
chapter = {
id: `chapter-${chapters.length+1}`,
name: tableItem.name,
nodes: []
}
chapters.push(chapter);
}
return tableItem
})
)
But how to combine getNodes which needs access to the whole scope of data?
I tried pipe but something is not working.
Example:
const getNodes = R.pipe(
R.path(['nodes']),
R.map((node) => {
console.log(node)
})
)
R.pipe(
getChapters,
getNodes
)(data)
Any help would be appreciated.

We could write something like this, using Ramda:
const {pipe, sortBy, prop, filter, map, applySpec, identity, propEq, find, __, addIndex, assoc} = R
const transform = ({tableItems, nodes}) => pipe (
filter (prop ('startingPoint')),
sortBy (prop ('pageNumber')),
map (applySpec ({
name: prop('name'),
nodes: pipe (prop('nodes'), map (pipe (propEq ('id'), find (__, nodes))), filter (Boolean))
})),
addIndex (map) ((o, i) => assoc ('id', `chapter-${i + 1}`, o))
) (tableItems)
const data = {tableItems: [{id: 1, name: "1", startingPoint: true, pageNumber: 15, nodes: [100, 200]}, {id: 2, name: "2", startingPoint: true, pageNumber: 14, nodes: [300, 400]}], nodes: [{id: 100, tableItemId: 1, content: "test"}, {id: 200, tableItemId: 1, content: "test"}, {id: 300, tableItemId: 2, content: "test"}, {id: 400, tableItemId: 2, content: "test"}]}
console .log (transform (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
First we filter the tableItems to include only those with startingPoint of true, then we sort the result by pageNumber. Then for each, we create name and nodes elements, based on the original data and on a function that maps the node values to the element in the initial nodes property. Finally, for each one, we add the chapter-# id element using addIndex (map).
This works, and is not horrible. It would take a fair bit of work to make this entirely point-free, I believe. And I don't find it worthwhile... especially because this Ramda version doesn't add anything to a simpler vanilla implementation:
const transform = ({tableItems, nodes}) =>
tableItems
.filter (x => x .startingPoint)
.sort (({pageNumber: a}, {pageNumber: b}) => a - b)
.map (({name, nodes: ns}, i) => ({
id: `chapter-${i + 1}`,
name,
nodes: ns .map (n => nodes .find (node => node .id == n)) .filter (Boolean)
}))
const data = {tableItems: [{id: 1, name: "1", startingPoint: true, pageNumber: 15, nodes: [100, 200]}, {id: 2, name: "2", startingPoint: true, pageNumber: 14, nodes: [300, 400]}], nodes: [{id: 100, tableItemId: 1, content: "test"}, {id: 200, tableItemId: 1, content: "test"}, {id: 300, tableItemId: 2, content: "test"}, {id: 400, tableItemId: 2, content: "test"}]}
console .log (transform (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
This works similarly to the above except that it assigns the id at the same time as name and nodes.
I'm a founder of Ramda and remain a big fan. But it doesn't always add anything to vanilla modern JS.

You can use a curried function. Because the pipe will always pipe the result of the previous function call into the next function. You can use R.tap if you want to step over.
However, I guess you want to have the data object and the output of the previous function call both in your getNodes function. In that case you can use a curried function, where you pass the response of the previous function as last parameter.
const getNodes = R.curryN(2, function(data, tableItemList){
console.log(tableItemList) // result of previous function call
return R.pipe(
R.path(['nodes']),
R.map((node) => {
console.log('node:', node);
})
)(data)
})
And use it like:
R.pipe(
getChapters,
getNodes(data)
)(data)

I would split the solution into two steps:
Prepare the tableItems and nodes to the required end state using R.evolve - filter, sort, and then use R.toPairs the tableItems to get an array that includes the index and the object. Group the nodes by id so you can pick the relevant nodes by id in the combine step.
Combine both properties to create the end result by mapping the new tableItems, and using R.applySpec to create the properties.
const {pipe, evolve, filter, prop, sortBy, toPairs, groupBy, map, applySpec, path, flip, pick} = R
const transform = pipe(
evolve({ // prepare
tableItems: pipe(
filter(prop('startingPoint')),
sortBy(prop('pageNumber')),
toPairs
),
nodes: groupBy(prop('id'))
}),
({ tableItems, nodes }) => // combine
map(applySpec({
id: ([i]) => `chapter-${+i + 1}`,
name: path([1, 'name']),
nodes: pipe(path([1, 'nodes']), flip(pick)(nodes)),
}))(tableItems)
)
const data = {tableItems: [{id: 1, name: "1", startingPoint: true, pageNumber: 15, nodes: [100, 200]}, {id: 2, name: "2", startingPoint: true, pageNumber: 14, nodes: [300, 400]}], nodes: [{id: 100, tableItemId: 1, content: "test"}, {id: 200, tableItemId: 1, content: "test"}, {id: 300, tableItemId: 2, content: "test"}, {id: 400, tableItemId: 2, content: "test"}]}
console.log(transform(data))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

Related

Apex Line Area chart is not getting displayed on the page in Vuejs

I am stuck on a page where i am not able to display the charts on the page.
To make it simplify what I have done is, here is the code sandbox:
I see there an error in console about the data, I am not sure about it.
https://codesandbox.io/s/compassionate-snyder-bckoq
I want to display the chart like this (as an example), but I am not able to display on the code sandbox
Please help.
The format of series is not aligned with ApexCharts.
You need to transform the data to match with ApexChart format.
Please see the changes in the codesandbox.
https://codesandbox.io/s/small-dew-eztod?file=/src/components/HelloWorld.vue
options: {
// X axis labels
xaxis: {
type: 'date',
categories: ["2021-05-04", "2021-05-05", "2021-05-07"]
},
},
series: [
{
name: "total",
data: [2, 2, 1],
},
{
name: "pending",
data: [0, 1, 0],
},
{
name: "approved",
data: [2, 1, 1],
},
{
name: "rejected",
data: [0, 0, 0],
},
],
Transform data to fit ApexChart
const data = {
"2021-05-04": {
total: 2,
pending: 0,
approved: 2,
rejected: 0,
},
"2021-05-05": {
total: 2,
pending: 1,
approved: 1,
rejected: 0,
},
"2021-05-07": {
total: 1,
pending: 0,
approved: 1,
rejected: 0,
},
};
const xaxis = {
type: "date",
categories: Object.keys(data).map((key) => key), // ['2021-05-04', '2021-05-05', '2021-05-07']
};
let statusObj = [];
for (const dataValue of Object.values(data)) { // get the values from keys '2021-05-04', '2021-05-05' ...
// loop the values, e.g. 1st loop: { total: 2, pending: 0, approved: 2, rejected: 0, }
for (const [key, value] of Object.entries(dataValue)) {
// take 'total' as example, find if statusObj already has { name: 'total', data: [x] }, e.g. statusObj = { name: 'total', data: [1] }
const existingStatusIndex = Object.keys(statusObj).find(
(sKey) => statusObj[sKey].name === key
);
// if yes, return the index of it
if (existingStatusIndex) {
// add new data value to existing data object. e.g. { name: 'total', data: [1, 2] }
statusObj[existingStatusIndex].data.push(value);
continue;
}
// if no, create a new object and add it to statusObj
statusObj.push({
name: key,
data: [value],
});
}
}
Output:
xaxis {
type: 'date',
categories: [ '2021-05-04', '2021-05-05', '2021-05-07' ]
}
statusObj [
{ name: 'total', data: [ 2, 2, 1 ] },
{ name: 'pending', data: [ 0, 1, 0 ] },
{ name: 'approved', data: [ 2, 1, 1 ] },
{ name: 'rejected', data: [ 0, 0, 0 ] }
]

Ramda - get array of objects using ids from other array

I have two sets of data and I would like to use the first one to get an array of objects from the second one. I tried to deal with it by myself but I am missing few steps.
Here is set of ids to use:
const idSet = {
"41": {
"id": "41"
},
"42": {
"id": "42"
},
"43": {
"id": "43"
}
}
And here is second set:
const nodes = {
"3": {
"nodeCommentId": 3,
"nodeId": 43,
},
"4": {
"nodeCommentId": 4,
"nodeId": 41
},
"6": {
"nodeCommentId": 6,
"nodeId": 42
},
"7": {
"nodeCommentId": 7,
"nodeId": 44
},
}
I need to search by id and nodeId so I tried to use something like this to get only ids from first set:
const ids = R.compose(
R.values(),
R.pluck('id')
)(idSet)
I also came up with something like: R.filter(R.compose(R.flip(R.contains)(ids), R.prop('nodeId')), nodes);
But then I have nodeId which is a number and not a string plus I need an array of objects without keys.
Desired output:
[
{
nodeCommentId: 3,
nodeId: 43
},
{
nodeCommentId: 4,
nodeId: 41
},
{
nodeCommentId: 6,
nodeId: 42
}
]
Any help will be appreciated.
This is probably too ugly to use, but it might be a start at a nice solution:
const nodesById = (idSet) => {
const ids = map (Number, pluck ('id') (values (idSet)))
return pipe (values, filter (pipe (prop('nodeId'), contains(__, ids))))
}
const idSet = {41: {id: "41"}, 42: {id: "42"}, 43: {id: "43"}}
const nodes = {3: {nodeCommentId: 3, nodeId: 43, }, 4: {nodeCommentId: 4, nodeId: 41}, 6: {nodeCommentId: 6, nodeId: 42}, 7: {nodeCommentId: 7, nodeId: 44}}
console .log (
nodesById (idSet) (nodes)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {map, pluck, values, pipe, filter, prop, contains, __} = R </script>
I'm sure that with a little work, we could make this entirely point-free, but I doubt that will help readability.
Transform the idSet to an array of numbers, and then user R.innerJoin to get the items with the matching nodeId:
const { pipe, values, pluck, map, innerJoin, __, curry } = R
const getIds = pipe(values, pluck('id'), map(Number))
const getNodesById = curry((idSet, nodes) =>
pipe(
values,
innerJoin(
({ nodeId }, id) => nodeId === id,
__,
getIds(idSet)
)
)(nodes)
)
const idSet = {41: {id: "41"}, 42: {id: "42"}, 43: {id: "43"}}
const nodes = {3: {nodeCommentId: 3, nodeId: 43, }, 4: {nodeCommentId: 4, nodeId: 41}, 6: {nodeCommentId: 6, nodeId: 42}, 7: {nodeCommentId: 7, nodeId: 44}}
const result = getNodesById(idSet)(nodes)
console.log(result)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

Is there a to set index of a data array within this.setState?

so lets say i have the following data
data: [
{ key: 1, id: 1, uri: "", image:false },
{ key: 2, id: 2, uri: "", image:false },
{ key: 3, id: 3, uri: "", image:false },
{ key: 4, id: 4, uri: "", image:false }
]
I want to update the uri using setState()and so far I have come up with the following using React.
`data: update(this.state.data, {1: {uri: {$set: result.uri}, image:{$set: true}}})`
however, instead of just having 1, I want to be able to pass in index, i know i can do this with if statements but im sure theres an easier and cleaner way??
You should make a copy of data, update it and then setState. For example:
copy
let newData = { ...this.state.data }
update
newData[0].uri = '';
newData[0].image = true
setState
this.setState({data: newData})

Duplicate objects based on prop list

I have a list of objects such this one:
var original = [
{
prop1: 1,
prop2: 2,
tags: ["tag1", "tag2"]
},
{
prop1: 3,
prop2: 4,
tags: ["tag1", "tag3", "tag4"]
},
{
prop1: 5,
prop2: 6,
tags: ["tag4"]
}
]
I want to duplicate the objects based on tags in order to finally have one object for every tag (also duplicated ones) using ramda.js.
var parsed = [
{
prop1: 1,
prop2: 2,
tags: ["tag1"]
},
{
prop1: 1,
prop2: 2,
tags: ["tag2"]
},
{
prop1: 3,
prop2: 4,
tags: ["tag1"]
},
{
prop1: 3,
prop2: 4,
tags: ["tag3"]
},
{
prop1: 3,
prop2: 4,
tags: ["tag4"]
},
{
prop1: 5,
prop2: 6,
tags: ["tag4"]
}
]
I tried with this function but I think there is some better solution
var f = (a,b) => R.evolve({tags: () => a}, b)
R.unnest(
R.map((v) =>
R.zipWith(f, v.tags, R.repeat(v, v.tags.length))
)(original)
)
You can do it like this:
const dup = pipe(
map(obj => map(tag => merge(obj, {tags: [tag]}), obj.tags)),
flatten
);
Or, perhaps more readably, like this:
const spread = obj => map(
tag => merge(obj, {tags: [tag]}
), obj.tags);
const dup = pipe(
map(spread),
flatten
);
While this probably could be made points-free with enough effort, it would likely be much uglier.
You can see this in action on the Ramda REPL.

lodash casting, mapping and picking

I have an array of objects that I am simplifying as this:
var expenses = [
{
arm: "0",
cat_id: "1",
crop: {
crop: "corn",
id: 1
},
crop_id: "1",
dist: "164.97",
expense: "Fertilizer",
id: "1",
loan_id: "1"
},
{
arm: "20",
cat_id: "8",
crop: {
crop: "corn",
id: 1
},
crop_id: "1",
dist: "0",
expense: "Labor",
id: "8",
loan_id: "1"
}
];
I am trying to end up with this:
var expenses = [{
arm: 0,
cat_id: 1,
crop: "corn",
crop_id: 1,
dist: 164.97,
expense: "Fertilizer",
id: 1,
loan_id: 1
},{
arm: 20,
cat_id: 6,
crop: "corn",
crop_id: 1,
dist: 0,
expense: "Labor",
id: 1,
loan_id: 1
}];
I can get certain pieces in that direction but can't pull it all together without error. I can't find out how to cast the values to float or put crop INSIDE of stub because casted returns all nulls. I currently have this:
flattened = _.map(expenses, function(item){
var crop = item.crop.crop;
var stub = _.pick(item, [
'id',
'loan_id',
'cat_id',
'expense',
'crop_id',
'arm',
'dist'
]);
var casted = _.map(stub, function(i){
i.crop = crop;
return i;
});
return stub;
});
Any help is appreciated.
Problem 1: I can't find out how to cast the values to float
This should be easily fixed by using parseFloat.
e.g.
item.dist = parseFloat(item.dist);
Problem 2: put crop INSIDE of stub because casted returns all nulls
Since you're already using lodash, might as well get used to their chaining feature (lazy evaluation).
DEMO
var flattened = _.map(expenses, function(item) {
item.dist = parseFloat(item.dist);
return _(item)
.omit('crop')
.assign(_.omit(item.crop, 'id'))
.value();
});
The solution above maps the entire expenses array, converting item.dist to a floating point value and then flattening the values from the item.crop object towards the item object with the exception of the item.crop.id value.
Note: In regards to your solution above, using _.map in an object
results to an array.
My attempt for my own learning purposes based on #ryeballar code.
var flattened = _.map(expenses, function(item) {
return _(item)
.set('crop', item.crop.crop) // set item.crop
.set('dist', parseFloat(item.dist)) // set dist as (float)item.dist
.value();
});