Accessing data inside compose function Ramda - javascript

I'm using a Ramda to give structure to some data. However, I have been unable to access the data inside the compose.
It should map items with a level greater than 2, but this did not work
[keys, compose(map(map(find(propEq('level', > 2)))), values)]
I am trying to keep all items inside typeChild as unique.
Here is the ramda console to test it out (got to follow link through there, SO won't allow goo.gl links): http://dpaste.com/0SATTZK
const result = pipe(
pluck('type'),
groupBy(
pipe(
find(propEq('level', 1)),
propOr('NoLevel', 'name'),
)
),
converge(
zipWith(unapply(zipObj(['name', 'typeChild']))),
[keys, compose(map(map(find(propEq('level', 2)))), values)]
),
);
result(data)
Input data
[{
"title": "Apple",
"type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
}, {
"title": "Tomato",
"type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
}, {
"title": "Potato",
"type": [{"name": "Food", "level": 1}, {"name": "Vegetable", "level": 2}]
}, {
"title": "The Alchemist",
"type": [{"name": "Entertainment", "level": 1}, { "name": "Book", "level": 2}]
}, {
"title": "Superman",
"type": [{"name": "Entertainment", "level": 1}, {"name": "Movie", "level": 2}]
}, {
"title": "More facts",
"type": [{"name": "Foo", "level": 2}]
}, {
"title": "Superman",
"type": [{"name": "Bar", "level": 1}]
}
];
Desired output
[
{name: "Food", typechild: [{level: 2, name: "Fruit"}, {level: 2, name: "Vegetable"}]},
{name: "Entertainment", typechild: [{level: 2, name: "Book"}, {level: 2, name: "Movie"}]},
{name: "NoName", typechild: [{level: 2, name: "Foo"}]},
{name: "Bar", typechild: []}
]

Ok, I'm going to make a guess at what you're looking for.
Show me the data
First of all, you really need to demonstrate some part of your input data. I reduced it to this:
const data =[{
"title": "Apple",
"type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
}, {
"title": "Tomato",
"type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
}, {
"title": "Potato",
"type": [{"name": "Food", "level": 1}, {"name": "Vegetable", "level": 2}]
}, {
"title": "The Alchemist",
"type": [{"name": "Entertainment", "level": 1}, { "name": "Book", "level": 2}]
}, {
"title": "Superman",
"type": [{"name": "Entertainment", "level": 1}, {"name": "Movie", "level": 2}]
}, {
"title": "More facts",
"type": [{"name": "Foo", "level": 2}]
}, {
"title": "Superman",
"type": [{"name": "Bar", "level": 1}]
}
];
(Note that I removed the color properties from each type as they don't seem relevant to the discussion, but they wouldn't change anything.)
And I'm guessing from your attempt that output something like this would be desired:
[
{name: "Food", typechild: [{level: 2, name: "Fruit"}, {level: 2, name: "Vegetable"}]},
{name: "Entertainment", typechild: [{level: 2, name: "Book"}, {level: 2, name: "Movie"}]},
{name: "NoName", typechild: [{level: 2, name: "Foo"}]},
{name: "Bar", typechild: []}
]
Approaching this by composing functions
Here is one approach:
const levelEq = (n) => pipe(prop('level'), equals(n));
const topLevel = pipe(prop('type'), find(levelEq(1)));
const topLevelName = pipe(topLevel, propOr('NoName', 'name'));
const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2)));
const convert = pipe(
groupBy(topLevelName),
map(extract2ndLevel),
map(uniq),
toPairs,
map(zipObj(['name', 'typechild']))
);
convert(data); //=> (the first output format above)
(Usually for those one-liners, instead of pipe, I would use compose, and reverse the order, but I also don't much like mixing compose and pipe in the same script. I definitely prefer pipe for the longer convert function. But switching any of these, or combining them would not change anything essential.)
The point is that this is built on function composition. Rather than trying to build it all at once, I write separate functions to do small jobs and combine them into more complex ones.
Note that this code will not gracefully handle bad data, and changing to do that might be a significant chore.
Note also that in the main function, I work one small step at a time. I can comment out subsequent steps to see the result of each individual step. I could also use R.tap if I liked.
Re-combining rarely a good idea
Each of those helper functions, except for the relatively simple levelEq is used only once. So they could be easily inlined. We could rewrite this code like this:
const convert = pipe(
groupBy(pipe(prop('type'), find(pipe(prop('level'), equals(1))), propOr('NoName', 'name'))),
map(pipe(pluck('type'), flatten, filter(pipe(prop('level'), gte(__, 2))), uniq)),
toPairs,
map(zipObj(['name', 'typechild']))
);
But to me that is an unreadable mess, and I wouldn't bother.
Better Documentation
If you are used to the Hindley-Milnar style type annotation, it might help to add type signatures to these functions, perhaps something like:
// Type :: {name: String, level: Int}
// :: Int -> (Type -> Bool)
const levelEq = (n) => pipe(prop('level'), equals(n));
// :: {type: [Type]} -> Type
const topLevel = pipe(prop('type'), find(levelEq(1)));
// :: {type: [Type]} -> String
const topLevelName = pipe(topLevel, propOr('NoName', 'name'));
// :: [{title: String, type: [Type}]}] -> [Type]
const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2)));
// [{title: String, type: [Type]}] -> [{name: String, typechild: [Type]}]
const convert = pipe( /* ... */ )
(If these mean nothing to you, don't worry about it.)
Changing Output Format
But perhaps you really want something like this:
[
{"name": "Food", "typechild": ["Fruit", "Vegetable"]},
{"name": "Entertainment", "typechild": ["Book", "Movie"]},
{"name": "NoName", "typechild": ["Foo"]},
{"name": "Bar", "typechild": []}
]
This turns out to be a simple change:
const convert = pipe(
groupBy(topLevelName),
map(extract2ndLevel),
map(uniq),
map(pluck('name')), // <--- A single addition
toPairs,
map(zipObj(['name', 'typechild']))
);
Advantages of map
One thing we see in that last snippet is a sequence of consecutive map calls. Each of those is looping over the list separately. This makes for clean code, but if in your performance testing, you found that this additional looping was causing your problems, you could take advantage of the composition law associated with map, which, suitably translated, says that
pipe(map(f), map(g)) ≍ map(pipe(f, g))
So you could add this:
// :: [{title: String, type: [Type}]}] -> [String]
const foo = pipe(extract2ndLevel, uniq, pluck('name'));
And rewrite the main function like this:
// [{title: String, type: [Type]}] -> [{name: String, typechild: [Type]}]
const convert = pipe(
groupBy(topLevelName),
map(foo),
toPairs,
map(zipObj(['name', 'typechild']))
);
But the fact that I can't think of a good name for this new function makes me think that it's not a great abstraction; I would only choose to do this if actual performance testing demonstrated that the multiple iterations were a real-world problem.
Conclusion
Functional programming is about many things, but one of the key techniques is a relentless breaking down of everything into easily understood pieces. That's what I try to do with this solution. While we can break this to create single functions without dependencies ("Recombining..." above) that is rarely readable. On the other hand, this approach made it easy to alter our approach ("Change Output formats"), and, if necessary, to fix performance problems ("Advantages of map").
Wow, that should have been a blog post!
You can see much of this in action on the Ramda REPL.

Related

Insert key's value in the array of object having same key

Say I've an array of object:
const Info = [{
"id": "1aa2",
"details": [{
"name": "rusty",
"age": "12",
"favourite": [ObjectId("602b696cb783fc15845d015e"), ObjectId("602b696cb783fc15845d0112")]
}]
},
{
"id": "3aa2",
"details": [{
"name": "john",
"age": "122",
"favourite": [ObjectId("602b696cb783fc15845d0112s"), ObjectId("602b696cb783fc15845d01wqs")]
}]
}
]
I want to merge favourite in one array as:
["favourite": [
ObjectId("602b696cb783fc15845d015e"),
ObjectId("602b696cb783fc15845d0112"),
ObjectId("602b696cb783fc15845d0112s"),
ObjectId("602b696cb783fc15845d01wqs")
]
]
I tried using for loop but it's creating nasty nested for loop which is reducing performance a lot.
Basically, you need to iterate through Info collection, iterate through 'details' sub-collection, and copy all data into a new array. After, just create a new structure using favs variable content or paste this code as object value directly.
BTW your result array need's to contain an object at least, like that:
[ { favourite: [...] } ]
About nested structures, you should try https://lodash.com/docs/4.17.15#flatMapDeep (at least just check the code)
const Info = [{
"id": "1aa2",
"details": [{
"name": "rusty",
"age": "12",
"favourite": ['ObjectId("602b696cb783fc15845d015e")', 'ObjectId("602b696cb783fc15845d0112")']
}]
},
{
"id": "3aa2",
"details": [{
"name": "john",
"age": "122",
"favourite": ['ObjectId("602b696cb783fc15845d0112s")', 'ObjectId("602b696cb783fc15845d01wqs")']
}]
}
]
const favs = Info.reduce((acc, item) => {
item.details.forEach(detail => {
acc.push(...detail.favourite);
})
return acc;
}, []);
console.log(favs);

Transforming a normalised redux object for rendering

I'm using Redux in my React Native project and would like some advice transforming data for rendering.
I have what I believe is a normalised userDetails object in the form:
"userDetails": Object {
"allIds": Array [
"111",
"222",
"333",
],
"byId": Object {
"111": Object {
"name": "Bob",
"part": "1st",
"section": "A",
},
"222": Object {
"name": "Alice",
"part": "2nd",
"section": "B",
},
"333": Object {
"name": "Fred",
"part": "1st",
"section": "B",
},
},
},
I'd like to render into a React Native SectionList with section as the section header and the list sorted alphanumerically first by section, then by part. A SectionList seems to require the data in the form:
const DATA = [
{
section: "A",
data: ["111"]
},
{
section: "B",
data: ["333", "222"]
}
];
Can anyone give me advice on how to get this done?
Use a library called reselect, which is designed for exactly this purpose

Ext JS - Combine 2 jsons from 2 endpoints into one Store

I have 2 endpoints, and each of them returns a JSON (actually 3 but let's make it simple). I can't combine them on the server side because I don't have access to it.
I need to display the data in an Ext.grid.Panel which accepts only one Store object. I tried making a model for each JSON and then somehow combining them, but I failed. So I'm thinking of joining them with a where clause, and I need to match the id from one JSON to bar_id from another JSON. For example like this:
{ "success": true,
"total": 3,
"message": "Data loaded",
"data": [
{"id": "1", "object_name": "foo1", "bar_id": 1},
{"id": "2", "object_name": "foo2", "bar_id": 2},
{"id": "3", "object_name": "foo3", "bar_id": 3}
]
}
And the other one:
{ "success": true,
"total": 5,
"message": "Data loaded",
"data": [
{"id": "1", "bar_name": "bar1"},
{"id": "2", "bar_name": "bar2"},
{"id": "3", "bar_name": "bar3"}
]
}
And I want to combine them like this:
[
{"id": "1", "object_name": "foo1", "bar_id": 1, "bar_name": "bar1"},
{"id": "2", "object_name": "foo2", "bar_id": 2, "bar_name": "bar2"},
{"id": "3", "object_name": "foo3", "bar_id": 3, "bar_name": "bar3"}
]
So I need something like: where FirstModel.bar_id equals SecondModel.id. Then I need to make a Store from this JSON. As you can see, I'm just starting with Ext JS.
Cao Urose,
Use one model based on combining your two response data arrays. Combining your two arrays is more a js issue than it is extjs issue. When you join your two data arrays, it will be straightforward to create a model and load your store.

Get item from array by ID using underscoreand and lodash library

I have this array:
var arrA = [
{"id": 1, "name": "Manager", "assignable": true},
{"id": 2, "name": "Developer", "assignable": true},
{"id": 3, "name": "Reporter", "assignable": true}
{"id": 4, "name": "Position", "assignable": true},
{"id": 5, "name": "Mayor", "assignable": true},
{"id": 6, "name": "Porter", "assignable": true}];
var arrB = [1,4,5];
I am using underscore.js and lodash.js in my project
What is elegant way to get from arrA array all items where id equals to items of arrB?
The most performant and elegant way is to use lodash's chaining to .indexBy() the items by ids, and then get the relevant items using .at():
var arrA = [
{"id": 1, "name": "Manager", "assignable": true},
{"id": 2, "name": "Developer", "assignable": true},
{"id": 3, "name": "Reporter", "assignable": true},
{"id": 4, "name": "Position", "assignable": true},
{"id": 5, "name": "Mayor", "assignable": true},
{"id": 6, "name": "Porter", "assignable": true}
];
var arrB = [1,4,5];
var returnedItems = _(arrA)
.indexBy('id')
.at(arrB)
.value();
document.getElementById('results').innerText = JSON.stringify(returnedItems);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<div id="results"></div>
Here's a lodash solution:
_.filter(arrA, _.flow(
_.identity,
_.property('id'),
_.partial(_.includes, arrB)
));
The filter() function returns a new array containing only the items we care about. We use the flow() function to build a callback that filters the items accordingly - here's a breakdown:
The identity() function returns the first argument that's passed to it. There's several arguments passed to the filter() callback, and we only care about the first one.
The property() function builds a function that returns the given property. In this case, we want to compare the id.
The last step is to check if the given id exists in arrB. To do that, we use partial() to create a new function that uses includes() to return true if the item exists.
This style is not for everyone, but it is compact, and not too gross.

Creating objects from array to json object

I have an array of object like this
[
{"id": "1", "name": "test"},
{"id": "2", "name": "test2"},
{"id": "3", "name": "test3"}
]
I want to convert it to object list this
{
"1": {"name": "test"},
"2": {"name": "test2"},
"3": {"name": "test3"},
}
You may use reduce :
var obj = arr.reduce(function(m,o){ m[o.id]={name:o.name}; return m }, {});
Side note : please be sure to read and try to understand T.J.'s comment about JSON

Categories

Resources