Reputation: 516
background: I'm fairly new to Ramda and FP. I find myself running into this scenario where I have two inputs itemList
and costList
. The values in the list have a relationship based on idx. So itemList[0]
and costList[0]
represent values that could be in the same object (e.g. { item: itemList[0], cost: costList[0]}
). So an alternative solution could begin with merging the itemList and costList. I'm interested in either or both solutions. Any other hints and suggestions would be appreciated as well.
let itemList = ['shirt', 'shoes'];
let costList = ['shirtCost', 'shoesCost'];
let formulaList = R.map(
x => ({
formulaXAttr: x,
formulaYAttr: 'tbd later',
})
)(itemList);
let finalList = R.map(
x => R.merge(x, {formulaYAttr: '???'}) // how to merge in costList?
)(formulaList);
// this is how i'd do it in vanilla JS
let finalList = formulaList.map((x, idx) => ({
...x,
formulaYAttr: costList[idx],
}));
Upvotes: 3
Views: 448
Reputation: 135237
To go along with the other answers here, I wish to show you how to write this kind of thing on your own. The Ramda library will not always have a one-line solution for you, and you hold yourself back if you limit your solutions to only use functions provided by Ramda.
This practice will give you the confidence to attempt to write your own program. When/if you discover that Ramda has some of the functionality you require, you can easily refactor your program to use the Ramda-supplied function.
const None =
Symbol ()
const zipWith = (f, [ x = None, ...xs ], [ y = None, ...ys ]) =>
x === None || y === None
? []
: [ f (x, y) ] .concat (zipWith (f, xs, ys))
console.log
( zipWith
( (name, price) => ({ name, price })
, [ 'shoes', 'pants' ]
, [ 19.99, 29.99 ]
)
)
// [ { name: "shoes", price: 19.99 }
// , { name: "pants", price: 29.99 }
// ]
Destructuring assignment used above creates helps maintain declarative style, but it does create unnecessary intermediate values. Below, we use an additional state parameter i
to lessen the memory footprint considerably.
const zipWith = (f, xs, ys, i = 0) =>
i >= xs.length || i >= ys.length
? []
: [ f (xs[i], ys[i]) ] .concat (zipWith (f, xs, ys, i + 1))
console.log
( zipWith
( (name, price) => ({ name, price })
, [ 'shoes', 'pants' ]
, [ 19.99, 29.99 ]
)
)
// [ { name: "shoes", price: 19.99 }
// , { name: "pants", price: 29.99 }
// ]
Note both implementations are pure. There are no side effects. Inputs are not mutated and a new Array is always constructed for the output.
Our implementation is also total, as it will always respond with a valid output when provided with a valid input.
console.log
( zipWith
( (name, price) => ({ name, price })
, [ 'shoes' ]
, [ 19.99 ]
)
// [ { name: "shoes", price: 19.99 } ]
, zipWith
( (name, price) => ({ name, price })
, [ 'shoes' ]
, []
)
// []
, zipWith
( (name, price) => ({ name, price })
, []
, [ 19.99 ]
)
// []
, zipWith
( (name, price) => ({ name, price })
, []
, []
)
// []
)
This time Ramda has your back with R.zipWith
, but next time it may not. Yet have no fear, because you got this! It becomes easy to write such procedures once you realize there's no magic in programming. And if you get stuck, I suppose there's always StackOverflow.
Upvotes: 2
Reputation: 50797
Scott Christopher's answer is clearly the correct one. zipWith
is designed for precisely this sort of scenario. In your title, you ask how to do this while mapping. I would like to point out that while Ramda does offer an option for this (see addIndex
for details), it is generally frowned upon. The reason for this is important for learning more about FP.
One understanding of map
is that it transforms a list of elements of one type into a list of elements of another type through the application of the given function to each element. This is a fine formulation, but there is a more general one: map
transforms a container of elements of one type into a container of elements of another type through the application of the given function to each element. In other words, the notion of mapping
can be applied to many different containers, not just lists. The FantatsyLand specification defines some rules for containers which have map
methods. The specific type is Functor
, but for those without the relevant mathematical background, this can be thought of as Mappable
. Ramda should interoperate well with such types:
const square = n => n * n;
map(square, [1, 2, 3, 4, 5]) //=> [1, 4, 9, 16, 25]
// But also, if MyContainer is a Functor
map(square, MyContainer(7)) //=> MyContainer(49)
// and, for example, if for some Maybe Functor with Just and Nothing subtypes
const {Just, Nothing} = {Maybe}
// then
map(square, Just(6)) //=> Just(36)
map(square, Nothing()) //=> Nothing()
Ramda's map
function calls the supplied transformation function with just the element of the container. It does not supply an index. This makes sense as not all containers have any notion of indices.
Because of this, mapping is Ramda is not the place for trying to match indices. But that is at the heart of Ramda's three zip functions. If you want to combine two lists whose elements are matched by index, you could do it most simply like Scott did with zipWith
. But you could also use zip
, which works like this:
zip(['a', 'b', 'c', 'd'], [2, 3, 5, 7]) //=> [['a', 2], ['b', 3], ['c', 5], ['d', 7]]
followed by a call to map
on the resulting pairs. zipWith
simply lets you do the same in a single step. But without it, primitives like map
and zip
can still be combined to do what you like.
Upvotes: 3
Reputation: 6516
The function you're looking for in Ramda is called zipWith
which takes a function expecting two arguments, one for each element of the first list along with it's pairwise element from the second list. This ends up as a list that is as long as the shorter of the two provided lists, containing the results of calling the function for each pair.
const itemList = ['shirt', 'shoes'];
const costList = ['shirtCost', 'shoesCost'];
const fn = R.zipWith((x, y) =>
({ formulaXAttr: x, formulaYAttr: y }))
console.log(
fn(itemList, costList)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Upvotes: 4