How can I switch from array to object data type in pipeline using ramda's reduce in point-free style?
I would like to achieve this:
(nodes) => nodes.reduce((acc, node: any) => {
acc[node.id] = {
out: node.outgoing_explicit,
in: node.incoming_explicit
};
return acc;
}, {})
Index the nodes by id, and the map them and use R.applySpec to change to the in/out format:
const { pipe, indexBy, map, applySpec, prop } = R
const fn = pipe(
indexBy(prop('id')),
map(applySpec({
out: prop('outgoing_explicit'),
in: prop('incoming_explicit'),
}))
)
const nodes = [{ id: 1, outgoing_explicit: 'abc1', incoming_explicit: 'xyz1' }, { id: 2, outgoing_explicit: 'abc2', incoming_explicit: 'xyz2' }, { id: 3, outgoing_explicit: 'abc3', incoming_explicit: 'xyz3' }]
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>
this could also be a solution:
const spec = {
in: R.prop('incoming_explicit'),
out: R.prop('outgoing_explicit'),
}
const fn = R.reduceBy(
R.flip(R.applySpec(spec)),
null,
R.prop('id'),
);
const data = [
{ id: 'a', incoming_explicit: 'Hello', outgoing_explicit: 'World' },
{ id: 'b', incoming_explicit: 'Hello', outgoing_explicit: 'Galaxy' },
{ id: 'c', incoming_explicit: 'Hello', outgoing_explicit: 'Universe' },
{ id: 'd', incoming_explicit: 'Hello', outgoing_explicit: 'Dimension' },
];
console.log(
fn(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>
The solutions involving applySpec are probably best, but just for variety, here's an alternative:
const convert = pipe (
map (juxt ([prop('id'), props(['incoming_explicit', 'outgoing_explicit'])])),
fromPairs,
map (zipObj (['in', 'out']))
)
const nodes = [{id: 'foo', outgoing_explicit: 43, incoming_explicit: 42}, {id: 'bar', outgoing_explicit: 20, incoming_explicit: 10}, {id: 'baz', outgoing_explicit: 309, incoming_explicit: 8675}]
console .log (convert (nodes))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {pipe, map, juxt, prop, props, fromPairs, zipObj} = R </script>
`juxt' is a bit of an oddball function. It works like this:
juxt([f, g, h, ...]) //=> (a, b, ...) -> [f(a, b, ...), g(a, b, ...), h(a, b, ...), ...]
Related
I'm trying to learn Ramda, but I'm struggling with seemingly simple stuff. How would I write the filter and sort using Ramda's pipe?
const items = [
{ id: 1, subitems: [{name: 'Foo', price: 1000}]},
{ id: 2, subitems: [{name: 'Bar'}]},
{ id: 3, subitems: [{name: 'Foo', price: 500}]},
{ id: 4, subitems: [{name: 'Qux'}]},
]
const findFoo = value => value.name === 'Foo'
items
.filter(item => item.subitems.find(findFoo))
.sort((a, b) => a.subitems.find(findFoo).price > b.subitems.find(findFoo).price ? -1 : 1)
// [{ id: 3, subitems: [...] }, { id: 1, subitems: [...] })
I've tried something like this but it returns an empty array:
R.pipe(
R.filter(
R.compose(
R.path(['subitems']),
R.propEq('name', 'Foo')
)
),
// Todo: sorting...
)(items)
Ramda's sortBy may help here. You could just do the following:
const findFoo = pipe (prop ('subitems'), find (propEq ('name', 'Foo')))
const fn = pipe (
filter (findFoo),
sortBy (pipe (findFoo, prop ('price')))
)
const items = [{id: 1, subitems: [{name: 'Foo', price: 1000}]}, {id: 2, subitems: [{name: 'Bar'}]}, {id: 3, subitems: [{name: 'Foo', price: 500}]}, {id: 4, subitems: [{name: 'Qux'}]}]
console .log (fn (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {pipe, prop, find, propEq, filter, sortBy} = R </script>
Obviously if we tried, we could make this entirely point-free, and address the concerns about double-extracting the Foo subobject. Here's a working version that converts the elements into [fooSubObject, element] pairs (where the former may be nil), then runs a filter to collect the elements where the fooSubObject is not nil, sorts by their price, then unwraps the elements from the pairs.
const fn = pipe (
map (chain (pair) (pipe (prop ('subitems'), find (propEq ('name', 'Foo'))))),
pipe (filter (head), sortBy (pipe (head, prop ('price')))),
map (last)
)
const items = [{id: 1, subitems: [{name: 'Foo', price: 1000}]}, {id: 2, subitems: [{name: 'Bar'}]}, {id: 3, subitems: [{name: 'Foo', price: 500}]}, {id: 4, subitems: [{name: 'Qux'}]}]
console .log (fn (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {pipe, map, chain, pair, prop, find, propEq, filter, last, sortBy, head} = R</script>
But to my eyes, this is a horrible, unreadable mess. We can tame it a bit by extracting a helper taking a function to generate the gloss object we will need for filtering and sorting, and our main process function (that actually does the filtering and sorting) using the gloss function to create the [gloss, element] pairs as above, calling our process and then extracting the second element from each resulting pair. As per Ori Drori's answer, we'll name that function dsu. It might look like this:
const dsu = (gloss, process) =>
compose (map (last), process, map (chain (pair) (gloss)))
const fn = dsu (
pipe (prop ('subitems'), find (propEq ('name', 'Foo'))),
pipe (filter (head), sortBy (pipe (head, prop ('price'))))
)
const items = [{id: 1, subitems: [{name: 'Foo', price: 1000}]}, {id: 2, subitems: [{name: 'Bar'}]}, {id: 3, subitems: [{name: 'Foo', price: 500}]}, {id: 4, subitems: [{name: 'Qux'}]}]
console .log (fn (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {compose, map, last, chain, pair, pipe, prop, find, propEq, filter, head, sortBy} = R</script>
This is better, and maybe marginally acceptable. But I still prefer the first version above.
You can use a DSU sort:
Decorate - map the original array, and create a tuple with the price of the found item in the sub-array, or -Infinity if none, and the original object.
Sort by using the price (the 1st item in the tuple).
Undecorate - Map again an extract the original object.
const { pipe, propEq, find, map, applySpec, prop, propOr, identity, sortWith, descend, head, last } = R
const findItemByProp = pipe(propEq, find)
const dsu = (value) => pipe(
map(applySpec([ // decorate with the value of the item in the sub-array
pipe(
prop('subitems'), // get subitems
findItemByProp('name', value), // find an item with the name
propOr(-Infinity, 'price') // extract the price or use -Infinity as a fallback
),
identity // get the original item
])),
sortWith([descend(head)]), // sort using the decorative value
map(last) // get the original item
)
const items = [{"id":1,"subitems":[{"name":"Foo","price":1000}]},{"id":2,"subitems":[{"name":"Bar"}]},{"id":3,"subitems":[{"name":"Foo","price":500}]},{"id":4,"subitems":[{"name":"Qux"}]}]
const result = dsu('Foo')(items)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
I am trying to add new properties width and height to nested objects.
My data structure looks like this:
const graph = {
id: 'root',
children: [
{
id: 'n1'
},
{
id: 'n2'
}
]
};
I am trying to add unique width and height properties to each child based on id
I tried R.lensPath. Here you can check it in ramda editor:
const widthLens = R.curry((id, data) => R.lensPath([
'children',
R.findIndex(R.whereEq({ id }),
R.propOr([], 'children', data)),
'width',
]));
const setWidth = widthLens('n1', graph);
R.set(setWidth, '100', graph);
And this is working almost as it should but it is adding only width plus I need to iterate over all children and return the same object with new properties. It also looks overcomplicated so any suggestions are more than welcome. Thank you.
There are several different ways of approaching this. But one possibility is to use custom lens types. (This is quite different from Ori Drori's excellent answer, which simply uses Ramda's lensPath.)
Ramda (disclaimer: I'm one of the authors) only supplies only a few specific types of lenses -- one for simple properties, another for array indices, and a third for more complex object paths. But it allows you to build ones that you might need. And lenses are not designed only for simple object/array properties. Think of them instead as a framing of some set of your data, something you can focus on.
So we can write a lens which focuses on the array element with a specific id. There are decisions to make about how we handle missing ids. I'll choose here -- if the id is not found -- to return undefined for a get and to append to the end on a set, but there are reasonable alternatives one might explore.
In terms of implementation, there is nothing special about id, so I will do this based on a specific named property and specialize it to id in a separate function. We could write this:
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensId = lensMatch ('id')
It would work like this:
const lens42 = lensId (42)
const a = [{id: 17, x: 'a'}, {id: 42, x: 'b'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
view (lens42, a) //=> {id: 42, x: 'b'}
set (lens42, {id: 42, x: 'z', foo: 'bar'}, a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'z', foo: 'bar'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
over (lens42, assoc ('foo', 'qux'), a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'b', foo: 'qux'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
But then we need to deal with our width and height properties. One very useful way to do this is to focus on an object with given particular properties, so that we get something like {width: 100, height: 200}, and we pass an object like this into set. It turns out to be quite elegant to write:
const lensProps = (props) => lens (pick (props), mergeLeft)
And we would use it like this:
const bdLens = lensProps (['b', 'd'])
const o = ({a: 1, b: 2, c: 3, d: 4, e: 5})
view (bdLens, o) //=> {b: 2, d: 4}
set (bdLens, {b: 42, d: 99}, o) //=> {a: 1, b: 42, c: 3, d: 99, e: 5}
over (bdLens, map (n => 10 * n), o) //=> {a: 1, b: 20, c: 3, d: 40, e : 5}
Combining these, we can develop a function to use like this: setDimensions ('n1', {width: 100, height: 200}, graph) We first write a lens to handle the id and our dimension:
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
And then we call the setter of this lens via
const setDimensions = (id, dimensions, o) =>
set (lensDimensions (id), dimensions, o)
We can put this all together as
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensProps = (props) => lens (pick (props), mergeLeft)
const lensId = lensMatch ('id')
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
const setDimensions = (id, dimensions, o) => set (lensDimensions (id), dimensions, o)
const graph = {id: 'root', children: [{id: 'n1'}, {id: 'n2'}]}
console .log (setDimensions ('n1', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{ id: "n1", height: 200, width: 100}, {id: "n2"}]}
.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 {find, propEq, findIndex, update, length, lens, pick, mergeLeft, compose, lensProp, set} = R </script>
This clearly involves more lines of code than does the answer from Ori Drori. But it creates the useful, reusable lens creators, lensMatch, lensId, and lensProps.
Note: This as is will fail if we try to work with unknown ids. I have a fix for it, but I don't have the time right now to dig into why it fails, probably something to do with the slightly unintuitive way lenses compose. If I find time soon, I'll dig back into it. But for the moment, we can simply change lensProps to
const lensProps = (props) => lens (compose (pick (props), defaultTo ({})), mergeLeft)
And then an unknown id will append to the end:
console .log (setDimensions ('n3', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{id: "n1"}, {id: "n2"}, {id: "n3", width : 100, height : 200}]}
You can use R.over with R.mergeLeft to add the properties to the object at the index:
const { curry, lensPath, findIndex, whereEq, propOr, over, mergeLeft } = R;
const graph = {"id":"root","children":[{"id":"n1"},{"id":"n2"}]};
const widthLens = curry((id, data) => lensPath([
'children',
findIndex(whereEq({ id }), propOr([], 'children', data)),
]));
const setValues = widthLens('n1', graph);
const result = over(setValues, mergeLeft({ width: 100, height: 200 }), graph);
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" referrerpolicy="no-referrer"></script>
const buttons = [
{ id: 1, text: 'First' },
{ id: 2, text: 'Second' },
{ id: 3, text: 'Third' }
]
const activeButtonIds = [1, 3]
Using lodash, I want to filter out all buttons with ids not included within activeButtonIds = [1, 3].
The obvious way of doing that is:
_.filter(buttons, ({ id }) => _.includes(activeButtonIds, id))
But I was wondering, is there a simpler way of achieving the same thing? A built-in function for this within lodash?
You can use _.intersectionWith() to find items that are included in both arrays using a comperator function:
const buttons = [{"id":1,"text":"First"},{"id":2,"text":"Second"},{"id":3,"text":"Third"}]
const activeButtonIds = [1, 3]
const result = _.intersectionWith(buttons, activeButtonIds, (button, activeId) =>
button.id === activeId
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
First of all, sorry for bad English.
I can't find any docs about this.
WHAT I WANT TO DO
const docs = {
type: 'a', // ['a',' 'b', 'c'] is available.
items: [
{
a: 123,
b: 100 // => This value only available when type is 'a' or 'b'. otherwise, forbidden.
}
]
};
MY JOI SCHEMA(NOT WORKED)
Joi.object({
type: Joi.string().valid('a', 'b', 'c').required(),
items: Joi.array()
.items(
Joi.object({
a: Joi.number().required()
b: Joi.number()
})
)
.when('type', {
is: Joi.string().valid('a', 'b'),
then: Joi.array().items(Joi.object({ b: Joi.number().required() })),
otherwise: Joi.array().items(Joi.object({ b: Joi.number().forbidden() }))
})
})
This code is not working correctly.
When type is 'c', it validate passed.
How can I fix this?
You have added .items() to items: Joi.array() which overrides the .when() condition, try using
Joi.object({
type: Joi.string().valid('a', 'b', 'c').required(),
items: Joi.array()
.when('type', {
is: Joi.string().valid('a', 'b'),
then: Joi.array().items(Joi.object({
a: Joi.number().required(),
b: Joi.number().required()
})),
otherwise: Joi.array().items(Joi.object({
a: Joi.number().required()
}))
})
})
example
Given the function below, how do I convert it to point-free style? Would be nice to use Ramda's prop and path and skip the data argument, but I just can't figure out the proper syntax.
const mapToOtherFormat = (data) => (
{
'Name': data.Name
'Email': data.User.Email,
'Foo': data.Foo[0].Bar
});
One option would be to make use of R.applySpec, which creates a new function that builds objects by applying the functions at each key of the supplied "spec" against the given arguments of the resulting function.
const mapToOtherFormat = R.applySpec({
Name: R.prop('Name'),
Email: R.path(['User', 'Email']),
Foo: R.path(['Foo', 0, 'Bar'])
})
const result = mapToOtherFormat({
Name: 'Bob',
User: { Email: 'bob#example.com' },
Foo: [{ Bar: 'moo' }, { Bar: 'baa' }]
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>
Here's my attempt:
const mapToOtherFormat = R.converge(
(...list) => R.pipe(...list)({}),
[
R.pipe(R.view(R.lensProp('Name')), R.set(R.lensProp('Name'))),
R.pipe(R.view(R.compose(R.lensProp('User'), R.lensProp('Email'))), R.set(R.lensProp('Email'))),
R.pipe(R.view(R.compose(R.lensProp('Foo'), R.lensIndex(0), R.lensProp('Bar'))), R.set(R.lensProp('Foo')))
]
)
const obj = {Name: 'name', User: {Email: 'email'}, Foo: [{Bar: 2}]}
mapToOtherFormat(obj)
Ramda console
[Edit]
We can make it completely point-free:
const mapToOtherFormat = R.converge(
R.pipe(R.pipe, R.flip(R.call)({})),
[
R.pipe(R.view(R.lensProp('Name')), R.set(R.lensProp('Name'))),
R.pipe(R.view(R.compose(R.lensProp('User'), R.lensProp('Email'))), R.set(R.lensProp('Email'))),
R.pipe(R.view(R.compose(R.lensProp('Foo'), R.lensIndex(0), R.lensProp('Bar'))), R.set(R.lensProp('Foo')))
]
)
Ramda console