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

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>

Related

Filter array based on a value in nested array with Ramda

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>

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: Filtering through arrays with associated value

This is my initial dataset:
arr1 = [{
url: ['https://example.com/A.jpg?', 'https://example.com/B.jpg?', 'https://example.com/C.jpg?'],
width: ['w=300', 'w=400', 'w=500'],
type: [-1, 1, 2]
}];
By filtering with type: n => n > 0 and passing the result through the arr1, I would like to produce arr2 with Ramda. If nth value is excluded as the result of the filter, then nth value in another arrays are also excluded.
arr2 = [{
url: ['https://example.com/B.jpg?', 'https://example.com/C.jpg?'],
width: ['w=400', 'w=500'],
type: [1, 2]
}];
I tried the code below, but not working...
const isgt0 = n => n > 0 ;
const arr2 = R.applySpec({
url : arr1,
width : arr1,
type : R.filter(isgt0),
});
console.log(arr2(arr1));
Once I get the desired object, I intend to R.transpose the array to generate an URL like: [https://example.com/B.jpg?w=400, https://example.com/C.jpg?w=500]
The main steps are:
Get the arrays of the values with R.props:
[-1, 1, 2]
['w=300', 'w=400', 'w=500']
['https://example.com/A.jpg?', 'https://example.com/B.jpg?', 'https://example.com/C.jpg?']
Transpose them to arrays of items with the same index:
[-1, 'w=300', 'https://example.com/A.jpg?']
[1, 'w=400', 'https://example.com/B.jpg?']
[1, 'w=500', 'https://example.com/C.jpg?']
Filter by index 0 (the original type), transpose back, and then reconstruct the object using R.applySpec.
const { pipe, props, transpose, filter, propSatisfies, gt, __, tranpose, applySpec, nth, map } = R
const filterProps = pipe(
props(['type', 'width', 'url']), // get an array of property
transpose, // convert to arrays of all property values with the same index
filter(propSatisfies(gt(__, 0), 0)), // filter by the type (index 0)
transpose, // convert back to arrays of each type
applySpec({ // reconstruct the object
type: nth(0),
width: nth(1),
url: nth(2),
})
)
const data = [
{
type: [-1, 1, 2],
width: ['w=300', 'w=400', 'w=500'],
url: [
'https://example.com/A.jpg?',
'https://example.com/B.jpg?',
'https://example.com/C.jpg?',
],
}
]
const result = map(filterProps, data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>
Another way to think about it more generically is to filter using a configuration object that holds the tests to apply for various properties. Here it is only type, but it's easy enough to imagine others.
My solution for this problem is configured with this object:
{
type: n => n > 0
}
This solutions uses many Ramda functions, but also uses Array.prototype.filter to have access to the index parameter of filter. We could choose R.addIndex instead, but I would only bother if I was trying to make it point-free, which doesn't seem worthwhile here. This is what it might look like:
const filterOnProps = (config) => (obj) => {
const test = allPass (map(([k, v]) => (i) => v (obj [k] [i]), toPairs (config)))
const indices = filter (test) (range (0, values (obj) [0] .length))
return map(a => a .filter ((_, i) => contains (i, indices)), obj)
}
const transform = map (filterOnProps ({type: n => n > 0}))
const arr1 = [{url: ['https://example.com/A.jpg?', 'https://example.com/B.jpg?', 'https://example.com/C.jpg?'], width: ['w=300', 'w=400', 'w=500'], type: [-1, 1, 2]}]
console .log (transform (arr1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {allPass, map, toPairs, filter, range, values, contains} = R </script>
With obj in scope, we create test, which will be somewhat equivalent to
allPass([
i => obj['type'][i] > 0
])
If we had more conditions in the original configuration object, they would also be in this list.
Then we filter the indices, to see on which ones the record passes this test.
Finally we map over our object, filtering each array to keep only those where the index is in the list.
While this should work, and is reasonably generic, it points to a problem with your data structure. I would suggest that as much as possible, you shy away from situations where structures are dependent on shared indices. To my mind the only reasonable use of that is for a relatively compact serialization format. On deserialization, I would immediately rehydrate that to something more useful, perhaps something like
const data = [
{url: 'https://example.com/A.jpg?', width: 'w=300', type: -1},
{url: 'https://example.com/B.jpg?', width: 'w=400', type: 1},
{url: 'https://example.com/C.jpg?', width: 'w=500', type: 2}
]
This structure is much easier to work with. For example, data.filter(({type}) => type > 0) would be the equivalent to the work above, if you started with this structure.
This might help a bit
const gte1 = R.filter(R.gte(R.__, 1));
const fn = R.map(
R.evolve({
type: gte1,
}),
);
// =====
const data = [
{
type: [-1, 1, 2],
width: ['w=300', 'w=400', 'w=500'],
url: [
'https://example.com/A.jpg?',
'https://example.com/B.jpg?',
'https://example.com/C.jpg?',
],
}
];
console.log(
fn(data),
);
<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, concat with propOr/pathOr

const obj = {
psets: [...],
type: {
psets: [...]
}
}
Want to concat the psets props. Both of them may not exist.
R.concat(R.pathOr([], ['type','pSets']), R.propOr([], 'pSets'));
**
Uncaught TypeError: function n(r){return 0===arguments.length||w(r)?n:t.apply(this,arguments)} does not have a method named "concat"
What am I doing wrong?
R.concat expects arrays or strings, and not functions. You can use R.converge to prepare the arrays for concat.
Note: R.__ is used as a placeholder for incoming arguments that you to assign to a different position than the last parameter.
const obj = {
pSets: [1, 2],
type: {
pSets: [3, 4]
}
}
const fn = R.converge(R.concat, [
R.pathOr([], ['type','pSets']),
R.propOr([], 'pSets')]
)
const result = fn(obj)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Another option that will make the code DRYer is to use R.chain to iterate the paths, get the the values from the object, and concat them:
const obj = {
pSets: [1, 2],
type: {
pSets: [3, 4]
}
}
const fn = R.curry((paths, obj) => R.chain(R.pathOr([], R.__, obj), paths))
const result = fn([['pSets'], ['type','pSets']], obj)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Transform objects pointfree style with Ramda

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