Filter array based on a value in nested array with Ramda - ramda.js

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>

Related

Item filtering but keeping track of filtered out items

Let's say I have a list of items like below and I would like to apply a list of filters onto it with ramda.
const data = [
{id: 1, name: "Andreas"},
{id: 2, name: "Antonio"},
{id: 3, name: "Bernhard"},
{id: 4, name: "Carlos"}
]
No biggie: pipe(filter(predA), filter(predB), ...)(data)
The tricky part is I would like to define my filters with a key for tracking what items have been filtered out by which filter.
const filterBy = (key, pred) => subs => {
const [res, rej] = partition(pred, subs)
return [{[key]: rej.map(prop('id'))}, res]
}
This all screams monad chaining or a transducer, but I can't get my head around it how to put it all together.
Let's say I have a 2 predicates:
const isEven = filterBy('id', i => i % 2 === 0)
const startsWithA = filterBy('name', startsWith('A'))
I would like to get a result that looks like this tuple with a rejection map and a list of "accepted" items (isEven threw out 1 and 3 and startsWithA rejected 3 and 4):
[
{
id: [1, 3],
name: [3, 4]
},
[{id: 2, name: "Antonio"}]
]
Vanilla JS version
I'm bothered by using the field name to describe the predicate. What happens if we also have, say, const nameTooLong = ({name}) => name .length < 8. Then how could we distinguish the two predicates in the output? So I would prefer to use descriptive predicate names, for instance,
[
{isEven: [1, 3], startsWithA: [3, 4]},
[{id: 2, name: "Antonio"}]
]
So that's what I do in this code:
const process = (preds) => (xs) => {
const rej = Object .fromEntries (Object .entries (preds)
.map (([k, v]) => [k, xs .filter (x => !v (x)) .map (x => x .id)])
)
const excluded = Object .values (rej) .flat()
return [rej, data .filter (({id}) => !excluded .includes (id))]
}
const data = [{id: 1, name: "Andreas"}, {id: 2, name: "Antonio"}, {id: 3, name: "Bernhard"}, {id: 4, name: "Carlos"}]
console .log (process ({
isEven: ({id}) => id % 2 === 0,
startsWithA: ({name}) => name .startsWith ('A')
}) (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
It would not be overly difficult to alter this to return something like your requested format.
Using Ramda
The question was tagged Ramda, and I wrote this initially using Ramda tools, with a version that looks like this:
const process = (preds) => (xs) => {
const rej = pipe (map (flip (reject) (xs)), map (pluck ('id'))) (preds)
const excluded = uniq (flatten (values (rej)))
return [rej, reject (pipe (prop ('id'), flip (includes) (excluded))) (data)]
}
And we could continue to hack away at this until we made it entirely point-free. I just don't see any reason for that.
I'm a founder of Ramda and a big fan, but I don't see this as any more readable than the vanilla version. There is one exception: Ramda's map working on a plain object is much nicer than the Object .entries -> map -> Object .fromEntries dance in the vanilla code. I might use that feature and leave the rest in vanilla, though.
Ok so after some fiddling I came up with this kind of solution. Implementing a new monad seemed unnecessary and overwriting fantasy-land/filter was also a bad idea, as my predicates are basically tagged.
This seems to have a good mix of readability and returns basically an extended array for further processing.
class Partition extends Array {
constructor(items, filtered = {}) {
super(...items)
this.filtered = filtered
}
filterWithKey = (key, pred) => {
const [ok, notOk] = partition(pred, this.slice())
const filtered = mergeDeepWith(concat, this.filtered, {[key]: notOk})
return new Partition(ok, filtered)
}
filter = pred => this.filterWithKey("", pred)
}
const res = new Partition([
{id: 1, name: "Andreas"},
{id: 2, name: "Antonio"},
{id: 3, name: "Bernhard"},
{id: 4, name: "Carlos"}
])
.filterWithKey('id', ({id}) => id % 2 === 0)
.filterWithKey('name', ({name}) => name.startsWith('A'))
const toIds = map(prop('id'))
const rejected = map(toIds, res.filtered)
const accepted = [...res]
console.log(rejected, accepted)

Ramda - how to add new properties to nested object

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>

lodash - is there a method to filter by array of single property?

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>

Ramda reduce from array to object?

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, ...), ...]

Vue - how to add reactive properties to objects in an array of objects?

I have an array of objects coming from the backend. I need to add additional data onto each object, to send.
My data coming in looks like this:
let arr = [
{
id: 1,
city: 'Orlando',
},
{
id: 2,
city: 'Miami',
},
{
id: 3,
city: 'Portland',
}
]
When the data comes loaded in through Vue, I need to have those properties be reactive. Meaning vue can see when those values change and call the appropiate lifecycle methods. See https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
let newArr = [
{
id: 1,
city: 'Orlando',
state: 'Fl',
country: 'USA',
},
{
id: 2,
city: 'Miami',
state: 'Fl',
country: 'USA',
},
{
id: 3,
city: 'Portland',
state: 'OR',
country: 'USA',
}
]
You can't just loop through and append properties to each object normally in vanillaJS, so how would you do this?
The vue docs suggests to use Object.assign when appending properties to make them reactive
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
But, this is only if you have to append one object.
For a list of objects, this is how you would initialize the data
newArr = arr.map(item => {
let newItem = Object.assign({}, item, {
state: undefined,
country: undefined,
});
return newItem;
});
Now all your objects properties in the array of objects are reactive
Vue also intercepts calls to Array.push, so you may like this better:
arr.forEach(item => newArr.push(item))
Quite late but thought of providing the answer as it can be useful to someone else in the future:
If you would like to add an additional property to a certain object within an array of objects then you can use something like this:
var searchId = 1
const cityInfo = arr.find(ele => ele.id === searchId)
Vue.set(identifiersNode, 'state', "Karnataka")
Vue.set(identifiersNode, 'country', "India")