#javascript #functional-programming #ramda.js
#javascript #функциональное программирование #ramda.js
Вопрос:
Я новичок в функциональном программировании и ramda. У меня есть случай, который можно решить императивным способом довольно просто, но я борюсь с декларативным способом. У меня есть следующая структура, которая описывает несколько инвентаризаций.
inventories = [
{
id: 'Berlin',
products: [{ sku: '123', amount: 99 }],
},
{
id: 'Paris',
products: [
{ sku: '456', amount: 3 },
{ sku: '789', amount: 777 },
],
},
]
Что я хочу сделать, так это преобразовать его в плоский список продуктов, который содержит дополнительную информацию, такую как inventoryId
, inventoryIndex
, и 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 },
]
Как я писал ранее, выполнение этого императивным способом не является проблемой.
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
}
Проблема в том, что я пытаюсь решить это с помощью ramda. Я понятия не имею, как получить доступ inventoryId
при отображении продуктов. Было бы здорово увидеть фрагмент кода, написанный с использованием ramda, который выполняет то же самое, что и приведенный выше.
Приветствия, Томаш
Ответ №1:
Вы можете сделать это легко с помощью 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);
Обратите внимание, что flatMap
это вызывается chain
в Ramda, но вам нужно addIndex
будет к нему.
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/ramda@0.27.1/dist/ramda.min.js"></script>
Комментарии:
1. Хотя это работает, не должны ли ваши внутренние вызовы логически быть
map
s вместоflatMap
/chainIndexed
?2. @ScottSauyet Хороший улов. Не знаю, почему это не вызвало ошибку типа.
3. Потому что это JS, и вещи, которые не должны работать, вроде как работают … пока они этого не делают!
4. Спасибо, ребята, это работает как шарм. У меня есть только один дополнительный вопрос. Можете ли вы рассматривать внутреннюю
mapIndexed
функцию как чистую, когдаinventory
для построения объекта используются «внешние» иinventoryIndex
переменные?5. @TomaszLempart Да, это чистая функция.
Ответ №2:
Я бы, вероятно, сделал это аналогично тому, что предложил Aadit, хотя я бы сформулировал это немного по-другому и использовал деструктурирование параметров
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}
Но, если вы хотите разбить это на более мелкие составные части, Ramda может помочь вам написать их и склеить в единое целое. Если мы попытаемся описать, что мы делаем, мы можем увидеть это как четыре шага. Мы переименовываем id
поле в inventoryId
, добавляем текущий индекс в наши инвентаризации и отдельные индексы для каждой группы products
, и мы денормализуем / выравниваем наши вложенные списки, способствуя products
слиянию массивов с их родителями.
Все это потенциально повторно используемые преобразования. Если бы мы захотели, мы могли бы выделить для них отдельные вспомогательные функции, а затем использовать Ramda для объединения их в единую функцию. Это может выглядеть так:
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>
Это требует больше кода, но то, что мы сделали, это создали ряд полезных небольших функций и сделали нашу основную функцию более декларативной, составив вызовы этих небольших функций. Это может стоить или не стоить делать в вашем проекте, но это, безусловно, стоит рассмотреть.