Reputation: 23
I am new to functional programming and ramda. I have a case, which can be solved in the imperative way quite easy, but I struggle with the declarative way. I have the following structure, which describes multiple inventories.
inventories = [
{
id: 'Berlin',
products: [{ sku: '123', amount: 99 }],
},
{
id: 'Paris',
products: [
{ sku: '456', amount: 3 },
{ sku: '789', amount: 777 },
],
},
]
What I want to do is to convert it into a flat list of products, which contains additional information like inventoryId
, inventoryIndex
, and productIndex
.
products = [
{ inventoryId: 'Berlin', inventoryIndex: 1, sku: '123', amount: 99, productIndex: 1 },
{ inventoryId: 'Paris', inventoryIndex: 2, sku: '456', amount: 3, productIndex: 1 },
{ inventoryId: 'Paris', inventoryIndex: 2, sku: '789', amount: 777, productIndex: 2 },
]
As I wrote previously, doing that in the imperative way is not a problem.
function enrichProductsWithInventoryId(inventories) {
const products = []
for (const [inventoryIndex, inventory] of inventories.entries()) {
for (const [productIndex, product] of inventory.products.entries()) {
product.inventoryId = inventory.id
product.inventoryIndex = inventoryIndex + 1
product.productIndex = productIndex + 1
products.push(product)
}
}
return products
}
The problem is when I try to solve that with ramda. I have no idea how to access inventoryId
, while mapping products. It would be great to see a piece of code written using ramda, which do the same as the one above.
Cheers, Tomasz
Upvotes: 0
Views: 553
Reputation: 74204
You can do this easily using flatMap
.
const inventories = [
{
id: "Berlin",
products: [{ sku: "123", amount: 99 }]
},
{
id: "Paris",
products: [
{ sku: "456", amount: 3 },
{ sku: "789", amount: 777 }
]
}
];
const products = inventories.flatMap((inventory, inventoryIndex) =>
inventory.products.map((product, productIndex) => ({
inventoryId: inventory.id,
inventoryIndex: inventoryIndex + 1,
sku: product.sku,
amount: product.amount,
productIndex: productIndex + 1
})));
console.log(products);
Note that flatMap
is called chain
in Ramda, but you'll need to addIndex
to it.
const inventories = [
{
id: "Berlin",
products: [{ sku: "123", amount: 99 }]
},
{
id: "Paris",
products: [
{ sku: "456", amount: 3 },
{ sku: "789", amount: 777 }
]
}
];
const chainIndexed = R.addIndex(R.chain);
const mapIndexed = R.addIndex(R.map);
const products = chainIndexed((inventory, inventoryIndex) =>
mapIndexed((product, productIndex) => ({
inventoryId: inventory.id,
inventoryIndex: inventoryIndex + 1,
sku: product.sku,
amount: product.amount,
productIndex: productIndex + 1
}), inventory.products), inventories);
console.log(products);
<script src="https://unpkg.com/[email protected]/dist/ramda.min.js"></script>
Upvotes: 2
Reputation: 50787
I would probably do this in a similar manner to what Aadit suggested, although I would frame it a bit differently, and use parameter destructuring
const convert = (inventories) =>
inventories .flatMap (({id: inventoryId, products}, i, _, inventoryIndex = i + 1) =>
products.map (
({sku, amount}, i, _, productIndex = i + 1) =>
({inventoryId, inventoryIndex, sku, amount, productIndex})
)
)
const inventories = [{id: "Berlin", products: [{sku: "123", amount: 99}]}, {id: "Paris", products: [{sku: "456", amount: 3}, {sku: "789", amount: 777}]}]
console .log (convert (inventories));
.as-console-wrapper {max-height: 100% !important; top: 0}
But, if you wanted to break this down into smaller component pieces, Ramda could help you write them and glue them together into a whole. If we try to describe what we're doing, we might see it as four steps. We rename the id
field to inventoryId
, we add a running index to our inventories and separate indices to each group of products
, and we denormalize/flatten our nested lists by promoting the products
arrays to merge with their parents.
All of those are potentially reusable transformations. If we wanted we could break out separate helper functions for these, and then use Ramda to compose them into a single function. It might look like this:
const {pipe, toPairs, map, fromPairs, addIndex, chain, merge, evolve} = R
const mapKeys = (cfg) => pipe (
toPairs,
map (([k, v]) => [cfg [k] || k, v]),
fromPairs
)
const addOrdinals = (name) =>
addIndex (map) ((x, i) => ({... x, [name]: i + 1 }))
const promote = (name) =>
chain (({[name]: children, ...rest}) => map (merge(rest), children))
const transform = pipe (
map (mapKeys ({id: 'inventoryId'})),
addOrdinals ('inventoryIndex'),
map (evolve ({products: addOrdinals ('productIndex')})),
promote ('products')
)
const inventories = [{id: "Berlin", products: [{sku: "123", amount: 99}]}, {id: "Paris", products: [{sku: "456", amount: 3}, {sku: "789", amount: 777}]}]
console .log (transform (inventories))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
This involves more code, but what we have done is to create a number of useful small functions and make our main function more declarative by composing calls to these small functions. It might or might not be worth doing in your project, but it's certainly worth considering.
Upvotes: 1