Is there lodash shortcut for spawning object from key name list and evaluator for them? - lodash

I often spawn Object from mapping certain field for their respective names, like this
_.zipObject(['A', 'B', 'C'], ['A', 'B', 'C'].map(evaluatorFN))
or
fields = ['A', 'B', 'C'];
_.zipObject(fields, fields.map(evaluatorFN))
This works, but I don't like that I have to save ['A', 'B', 'C'] in extra var, cause this is less explicit(if it's unique), and, apparently writing 2 times is even worse idea.
Is there something in lodash, which would do the same, but would be called like
_.constructFromArrayFN(['A', 'B', 'C'], evaluatorFN)
I cannot find anything that would work as constructFromArrayFN in lodash, does it exist?

Lodash doesn't have a function for this specific case, but you can easily create one:
const zipValues = (fields, evaluatorFN) =>
_.zipObject(fields, fields.map(evaluatorFN))
const result = zipValues(['A', 'B', 'C'], str => str.repeat(3))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
If you are using lodash globally, and not via imports, you can use _.mixin() to add the function to the global lodash (_):
_.mixin({
zipValues(fields, evaluator){
return _.zipObject(fields, fields.map(evaluator))
}
});
const result = _.zipValues(['A', 'B', 'C'], str => str.repeat(3))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

Related

Ramda - how to get multiple properties from array

I am trying to get two properties from an array of objects which looks like this:
const nodes = [
{
"id": "0",
"outgoingNodeIds": ['1', '2'],
"outgoingVirtualNodes": [
{
"virtualNodeId": "5",
},
{
"virtualNodeId": "10",
}
],
}
]
I need to get outgoingNodeIds which is a simple array of strings and outgoingVirtualNodes which is an array of objects and I need to get virtualNodeId from it.
There is no problem with getting only one property at the time and then concat two arrays (example in ramda):
const prop1 = R.pipe(
R.pluck('outgoingNodeIds'),
R.flatten
)(nodes)
const prop2 = R.pipe(
R.pluck('outgoingVirtualNodes'),
R.map(R.pluck('virtualNodeId')),
R.flatten
)(nodes)
R.concat(prop1, prop2)
But I would like to combine this into one pipe if possible. I know how to get two properties at once but I don't know how to map the second one to get virtualNodeId.
Any help would be appreciated. Thank you
Use R.ap to apply a list of functions to the array - each function produces a new array of values from all object objects in the original array, and then flatten to a single array. The end result would be an array of all outgoingNodeIds from all objects, and then the virtualNodeId from all objects:
const { pipe, ap, prop, pluck, flatten } = R
const fn = pipe(
ap([
prop('outgoingNodeIds'),
pipe(prop('outgoingVirtualNodes'), pluck('virtualNodeId'))
]),
flatten,
)
// Note that are two objects - a and b
const nodes = [{"id":"0","outgoingNodeIds":["1a","2a"],"outgoingVirtualNodes":[{"virtualNodeId":"5a"},{"virtualNodeId":"10a"}]},{"id":"1","outgoingNodeIds":["3b","4b"],"outgoingVirtualNodes":[{"virtualNodeId":"15b"},{"virtualNodeId":"20b"}]}]
const result = fn(nodes)
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>
If you want the outgoingNodeIds and then virtualNodeId of each object together replace R.ap with R.map and R.juxt. The R.juxt apply a list of functions to a list of parameters (a single object in this case).
const { pipe, map, juxt, prop, pluck, flatten } = R
const fn = pipe(
map(juxt([
prop('outgoingNodeIds'),
pipe(prop('outgoingVirtualNodes'), pluck('virtualNodeId'))
])),
flatten,
)
// Note that are two objects - a and b
const nodes = [{"id":"0","outgoingNodeIds":["1a","2a"],"outgoingVirtualNodes":[{"virtualNodeId":"5a"},{"virtualNodeId":"10a"}]},{"id":"1","outgoingNodeIds":["3b","4b"],"outgoingVirtualNodes":[{"virtualNodeId":"15b"},{"virtualNodeId":"20b"}]}]
const result = fn(nodes)
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>
Since we're dealing with a single object on each iteration of R.map, you can replace R.juxt with R.applySpec:
const { pipe, map, applySpec, prop, pluck, flatten } = R
const fn = pipe(
map(applySpec([
prop('outgoingNodeIds'),
pipe(prop('outgoingVirtualNodes'), pluck('virtualNodeId'))
])),
flatten,
)
// Note that are two objects - a and b
const nodes = [{"id":"0","outgoingNodeIds":["1a","2a"],"outgoingVirtualNodes":[{"virtualNodeId":"5a"},{"virtualNodeId":"10a"}]},{"id":"1","outgoingNodeIds":["3b","4b"],"outgoingVirtualNodes":[{"virtualNodeId":"15b"},{"virtualNodeId":"20b"}]}]
const result = fn(nodes)
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>
Ori Drori has already given several great Ramda versions. But let's not forget how simple this can be in modern JS:
const extractNodeIds = (nodes) => nodes .flatMap ((node) => [
... (node .outgoingNodeIds),
... (node .outgoingVirtualNodes .map (vn => vn .virtualNodeId))
])
const nodes = [{id: '0', outgoingNodeIds: ['1', '2'], outgoingVirtualNodes: [{virtualNodeId: '5'}, {virtualNodeId: '10'}]}]
console .log (extractNodeIds (nodes))
Ramda was designed and mostly built in the days before ES6 was ubiquitous. But these days, tools like flatMap and the array ...spread syntax make many problems that Ramda solved so nicely almost as nice in plain JS. It's not that I don't love the tool -- I'm a Ramda founder and a big fan -- but it no longer seems as necessary as it once did.

How to get React Native Pressable onPress to manipulate target

I have an array filled with the letters of the alphabet that I have turned into Pressable components. My goal is to change the color of the letter clicked depending on some state. React Native's Pressable onPress prop accepts a function with an event argument. Sadly you can't simply do the classic event.target.style.color in order to manipulate the target like you can in vanilla js. This is what I have tried so far:
const onPressLetter = (evt) => {
let targetStyle = evt._dispatchInstances.memoizedProps.children[0].props.style;
shadeLetter ? targetStyle.color = 'rgba(0, 0, 0, 0.5)' : targetStyle.color = 'green';
}
The color change isn't taking place when clicked. Console logging targetStyle before and after where we change the color even reflects that the style object has changed. What am I doing wrong? How can i accomplish this in react native??
Keep in mind I dont want to use the style prop for Pressable because I want the color change to stay after click
Induce a bijection between letters and boolean states, which you can keep in a single state array. Provide the onPressLetter function with an index and toggle the corresponding state.
Here is a minimal example using map. In a real application, I would use a FlatList. The workflow stays the same.
const alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
const Screen = (props) => {
const [pressedLetters, setPressedLetters] = useState(alphabet.map(letter => false))
function onPressLetter(i) {
setPressedLetters(prev => prev.map((item, index) => index === i ? !item : item))
}
return (
alphabet.map((item, index) => {
return <Pressable onPress={() => onPressLetter(index)}>
<Text style={ {color: pressedLetters[index] ? 'green' : 'rgba(0, 0, 0, 0.5)'}}>{item}</Text>
</Pressable>
})
)
}

Ramda zipWith, one argument

A function for telling if a person has any given pets.
const person = {
name: 'joe',
pets: {
dog: true,
cat: false,
fish: true
}
}
const personHasPet = (pet: string) => R.pipe(R.path('pets', pet), R.equals(true));
const personHasPets = (listOfPets: string[]) => R.allPass(R.zipWith(personHasPet, listOfPets, listOfPets))(person);
This works. However, as you can see, I supply zipWith with two arguments. Thats because zipWith requires two argument. Why is this?
I only need one since I only have one argument in persHasPet function. zipWith always requires two? Why is this? Is there any other function when you have one argument? And another when you have three? Makes no sense.
https://ramdajs.com/docs/#zipWith
It's not entirely clear what you're trying to accomplish with zipWith, but the function supplied is definitely designed for working with two lists, and the callback should definitely take two parameters. Thus:
zipWith(fn, [a1, a2, a3, ... an], [b1, b2, b3, ... bn])
//=> [fn(a1, b1), fn(a2, b2), fn(a3, b3), ... fn(an, bn)]
This is what it does, "zipping" together two lists by applying a function to each pair.
I think the code you're looking for might be something like the following, but I'm not really certain:
const personHasPet = compose (path, flip (append) (['pets']) )
const personHasPets = compose (allPass, map (personHasPet) )
const joe = {name: 'joe', pets: {dog: true, cat: false, fish: true}}
console .log (
personHasPet ('dog') (joe), //=> true
personHasPet ('cat') (joe), //=> false
personHasPet ('fish') (joe), //=> true
)
console .log (
personHasPets (['dog', 'cat']) (joe), //=> false
personHasPets (['dog', 'fish']) (joe), //=> true
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {compose, path, flip, append, allPass, map} = R </script>
I'm also not sure if that Ramda implementation gains anything over the vanilla
const personHasPet = (pet) => (person) => person .pets && person .pets [pet]

Functional programming RamdaJs groupBy with transformation

I want to create function that groups the array with specific key as follow:
var items = [
{name: 'n1', prop: 'p1', value: 90},
{name: 'b', prop: 'p2', value: 1},
{name: 'n1', prop: 'p3', value: 3}];
Into this:
{n1: {p1: 90, p3: 3}, {b: {p2: 1}
Basically group by column "name" and sets the prop name as key with the value.
I know there is groupBy function in RamdaJs but it accepts function to generate the group key.
I know I can format the data after that but I will be inefficient.
Is there any way to pass some kind of "transform" function which prepare the data for each item.
Thanks
There is a trade-off involved in using a generic library and writing custom code for every scenario. A library like Ramda with several hundred functions will offer many tools that can help, but they are not likely to cover every scenario. Ramda does have a specific function to combine groupBy with some sort of fold, reduceBy. But if I didn't know that, I would write a custom version.
I would start with what works and remains simple, only worrying about performance if tests showed an issue with this specific code. Here I show a number of steps of changing such a function each time to improve performance. I'll make the main point here: I would actually stick with my first version, which I find easily readable, and not bother with any of the performance enhancements unless I had hard numbers to show that this was a bottleneck in my application.
Plain Ramda version
My first pass might look like this:
const addTo = (obj, {prop, value}) =>
assoc (prop, value, obj)
const transform1 = pipe (
groupBy (prop ('name')),
map (reduce (addTo, {}))
)
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}];
console .log (
transform1 (items)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {assoc, pipe, groupBy, prop, map, reduce} = R </script>
Only Loop Once
This to me is clear and easy to read.
But there is certainly a question of efficiency, given that we have to loop over the list to group and then loop over each group to fold. So perhaps we'd be better off with a custom function. Here's a fairly straightforward modern JS version:
const transform2 = (items) =>
items .reduce(
(a, {name, prop, value}) => ({...a, [name]: {...a[name], [prop]: value}}),
{}
)
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}];
console .log (
transform2 (items)
)
Don't reduce ({...spread})
This version only loops once, which sounds like a nice improvement... but there is a real question about the performance of what Rich Snap calls the reduce ({...spread}) anti-pattern. So perhaps we want to use a mutating reduce instead. This shouldn't cause problems as it's only internal to our function. We can write an equivalent version that doesn't involve this reduce ({...spread}) pattern:
const transform3 = (items) =>
items .reduce (
(a, {name, prop, value}) => {
const obj = a [name] || {}
obj[prop] = value
a[name] = obj
return a
},
{}
)
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}];
console .log (
transform3 (items)
)
More Performant Looping
Now we've removed that pattern (I don't in fact agree that it's always an anti-pattern), we have a more performant bit of code, but there is still one thing we can do. It's well known that the Array.prototype functions such as reduce are not as fast as their plain loop counterparts. So we can go one step further and write this with a for-loop:
const transform4 = (items) => {
const res = {}
for (let i = 0; i < items .length; i++) {
const {name, prop, value} = items [i]
const obj = res [name] || {}
obj[prop] = value
}
return res
}
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}]; console.log('This version is intentionally broken. See the text for the fix.');
console .log (
transform4 (items)
)
We've reached the limit of what I can think of in terms of performance optimizations.
... And we've made the code much worse! Comparing that last version with the first,
const transform1 = pipe (
groupBy (prop ('name')),
map (reduce (addTo, {}))
)
we see a hand-down winner in terms of code clarity. Without knowing the details of the addTo helper, we can still get a very good sense up-front, of what this function does on first reading. And if we want those details more obvious, we could simply in-line that helper. Version, though, will take a careful reading to understand how it works.
Oh wait; it doesn't work. Did you test it and see that? Do you see what's missing? I pulled this line from the end of the for-loop:
res[name] = obj;
Did you notice that in the code? It's not particularly difficult to spot, but it's not necessarily obvious at a quick glance.
Summary
Performance optimization, when it's needed, has to be done very carefully, as you can't take advantage of many of the tools you get used to using. So, there are times when it's very important, and I do it then, but if my cleaner, easier-to-read code performs well enough, then I'll leave it there.
Point-free (pointless?) Aside
A similar argument applies for pushing too hard for point-free code. It's a useful technique, and many functions become cleaner by using it. But it can be pushed beyond its usefulness. Note that the helper function, addTo, from the initial version above is not point-free. We can make a point-free version of it. There may be simpler ways, but the first thing that comes to my mind is pipe (lift (objOf) (prop ('prop'), prop ('value')), mergeAll). We could write an entirely point-free version of this function in-lining that this way:
const transform5 = pipe (
groupBy (prop ('name')),
map (pipe (
map (lift (objOf) (
prop ('prop'),
prop ('value')
)),
mergeAll
))
)
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}];
console .log (
transform5 (items)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {pipe, groupBy, prop, map, lift, objOf, mergeAll} = R </script>
Does this gain us anything? Not that I can see. The code is much more complex and much less expressive. This is as hard to read as the for-loop variant.
So again, focus on keeping the code simple. That's my advice, and I'm sticking to it!
I would use reduceBy instead:
it allows a function to generate a key
and a function to transform the data
const items = [
{name: 'n1', prop: 'p1', value: 90},
{name: 'b', prop: 'p2', value: 1},
{name: 'n1', prop: 'p3', value: 3}];
// {name: 'n1', prop: 'p1', value: 90} => {p1: 90}
const kv = obj => ({[obj.prop]: obj.value});
// {p1: 90}, {name: 'n1', prop: 'p3', value: 3} -> {p1: 90, p3: 3}
const reducer = (acc, obj) => mergeRight(acc, kv(obj));
console.log(
reduceBy(reducer, {}, prop('name'), items)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {reduceBy, prop, mergeRight} = R;</script>
An imperative for...of loop, with a bit of destruction is readable, albeit verbose, and performant.
const fn = arr => {
const obj = {}
for(const { name, prop, value } of arr) {
if(!obj[name]) obj[name] = {} // initialize the group if it doesn't exist
obj[name][prop] = value // add the prop and it's value to the group
}
return obj
}
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}]
const result = fn(items)
console.log(result)
A functional solution using Ramda would be slower but depending on the number of items in the array it might be negligible. I usually start with a functional solution, and only if I have performance issues, I profile, and then fallback to the more performant imperative option.
A readable pointfree solution using Ramda - R.groupBy and R.map would be the basis. In this case I map each group items to their props, and then use R.fromPairs to generate the object.
const { pipe, groupBy, prop, map, props, fromPairs } = R
const fn = pipe(
groupBy(prop('name')),
map(pipe(
map(props(['prop', 'value'])),
fromPairs
))
)
const items = [{name: 'n1', prop: 'p1', value: 90}, {name: 'b', prop: 'p2', value: 1}, {name: 'n1', prop: 'p3', value: 3}]
const result = fn(items)
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

combining performing _.uniq with _.isEqual in lodash

lodash provides a method _.uniq() to find unique elements from an array, but the comparison function used is strict equality ===, while I want to use _.isEqual(), which satisfies:
_.isEqual([1, 2], [1, 2])
// true
Is there a way to perform _.uniq() with _.isEqual(), without writing my own method?
As of lodash v4 there's _.uniqWith(array, _.isEqual).
from the docs:
var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
_.uniqWith(objects, _.isEqual);
// → [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
more info: https://lodash.com/docs#uniqWith
While you can't use isEqual() with uniq(), what you're asking for seems perfectly reasonable and deserves a solution. Here's the best (clean/performant) I could come up with:
_.reduce(coll, function(results, item) {
return _.any(results, function(result) {
return _.isEqual(result, item);
}) ? results : results.concat([ item ]);
}, []);
You can use reduce() to build an array of unique values. The any() function uses isEqual() to check if the current item is already in the results. If not, it adds it.